I want to make the next array based on another 2 arrays:
array1 = ['a', 'b']
array2 = [1,2,3]
I want to create the next array
newArray = [['a',1], ['a',2], ['a',3], ['b',1], ['b',2], ['b',3]]
Here is my code:
var test1 = ['a', 'b'];
var test2 = [1,2,3], arr1 = [], arr2 = [];
for(var i = 0; i < test1.length; i++){
arr1 = [];
arr1.push(test1[i]);
for(var x = 0; x < test2.length; x++){
if(arr1.length > 1)
arr1.pop();
arr1.push(test2[x])
arr2.push(arr1);
}
}
console.log("arr1:",JSON.stringify(arr1), "arr2:" ,JSON.stringify(arr2));
But it returns the last element of the second array.
[['a',3], ['a',3], ['a',3], ['b',3], ['b',3], ['b',3]]
Why is this happening?
8 Answers 8
Every other answer about array lengths, and similar things are not right. The only reason you get 3 (or whatever the last value/length is) all over the place is because Arrays are by reference, and functions, not for-loops create lexical scope. This is one of the reasons you hear that 'functions are first-class citizens in javascript'. This is a classical example, and frequently in interviews too, used to trip up devs who are not used to how scoping in javascript really behaves. There are some ways to fix it that involve wrapping the innards of loops in functional scopes, and passing in the index, but I'd like to suggest a more 'javascript centric' approach, and that is to solve the problem with functions.
See this example (which by the way is also a clear way to implement your goal.)
var test1 = ['a', 'b'];
var test2 = [1,2,3];
// this will iterate over array 1 and return
// [[ [a,1],[a,2],[a,3] ],[ [b,1],[b,2],[b,3] ]]
var merged = test1.map(function(item1){
return test2.map(function(item2){
return [item1, item2];
});
});
//this is a slick way to 'flatten' the result set
var answer = [].concat.apply([],merged )
console.log(answer) //this is it.
Functions () make scope - not 'brackets' {}. The easiest fix is usually to use functions to solve your problems as they create lexical scope. The most trusted library on npm, lodash, for instance is based on this. I think you'll write less and less loops from day to day js as you progress, and use more functions.
Working example on js fiddle: https://jsfiddle.net/3z0hh12y/1/
You can read more about scopes and closures here https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch1.md
And one more thing: when you think you want a loop in js, you usually want Array.map(), especially if you're remapping values.
9 Comments
return [item1, item2] to create the array as OP asked?rx.js, most.js, highland.js and a lot of other libraries do this also. That answer is similar how many serious libs do it - the only reason I did not write exactly that is because I wanted to explain the 'why' of it, and answer in native JS (for anyone coming in). Currently for observables and functional transformation, most.js is the fastest I am aware of at the moment. Take a look at motorcyle.js for an example in FRP context.You problem lies in the second loop. because you have a single reference to ary1 you are over writing the value with each loop.
so first time it loops your array will have
[['a',1]]
but because you you only have one reference to ary1 you are just editing the value you just pushed into ary2.
so then you get this:
[['a',2],['a',2]]
you may think that you are pushing a new array in but it is in fact the same exact array! so the pattern continues and you get the result that you are seeing.
Comments
It's occurring because you're popping elements off arr1 once it's length exceeds 1, so the last element is all that persists.
Try this:
var test1 = ['a', 'b'];
var test2 = [1,2,3];
var arr1 = [], arr2 = [];
for(var i = 0; i < test1.length; i++) {
for(var x = 0; x < test2.length; x++) {
arr1[arr1.length] = [test1[i], test2[x]];
}
}
console.log(JSON.stringify(arr1));
Comments
Beside the solutions for fixed style for two array, you might use another approach to get the result. you could use an iterative and recursive approach for variable length of parts an their length.
function combine(array) {
function c(part, index) {
array[index].forEach(function (a) {
var p = part.concat([a]);
if (p.length === array.length) {
r.push(p);
return;
}
c(p, index + 1);
});
}
var r = [];
c([], 0);
return r;
}
console.log(combine([['a', 'b'], [1, 2, 3]]));
console.log(combine([['a', 'b', 'c'], ['1', '2', '3', '4'], ['A', 'B']]));
console.log(combine([['a', 'b', 'c'], ['1', '2', '3', '4'], [['A'], ['B']]]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
4 Comments
var p = part.concat([a]); maybe this helps in your problem as well (concar reduces only one level of the array).concat. right, it looks a bit hacky.I guess the proper functional approach would be done by a single liner reduce and nested map duo.
var arr = ['a', 'b'],
brr = [1,2,3],
result = arr.reduce((p,c) => p.concat(brr.map(e => [c,e])),[]);
console.log(JSON.stringify(result));
Comments
your approach is a bit off, you better take this approach (which is pretty simple I think): DEMO
var array1 = ['a', 'b'],
array2 = [1,2,3],
nextArray=[];
for(var i=0, array1Length=array1.length; i < array1Length; i++){
for(var x=0, array2Length=array2.length; x < array2Length; x++){
nextArray.push([array1[i],array2[x]]);
}
}
console.log(nextArray);
1 Comment
Yeah, that's way more complicated than it needs to be. The result you want is called the Cartesian product, and can be generated like this:
const cartesianProduct = (a, b) => {
const rv = [];
a.map(ae => b.map(be => rv.push([ae, be])));
return rv;
};
const array1 = ['a', 'b']
const array2 = [1,2,3]
console.log(JSON.stringify(cartesianProduct(array1, array2)));
If Javascript6 had flatMap it would be even simpler:
const cartesianProduct = (a, b) =>
a.flatMap(ae => b.map(be => [ae, be]));
4 Comments
I modified your code a little (but not too much, I tried to keep the same approach). The solution was to create a new array (arr1) in the inner loop. Otherwise, the first correct pair ['a', 1] would get overridden just after begin pushed to arr2 at the next loop pass.
var test1 = ['a', 'b'];
var test2 = [1,2,3];
var arr2 = [];
for(var i = 0; i < test1.length; i++){
for(var x = 0; x < test2.length; x++){
var arr1 = [];
arr1.push(test1[i]);
if(arr1.length > 1){
arr1.pop();
}
arr1.push(test2[x]);
arr2.push(arr1);
}
}