2

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?

sabithpocker
15.6k1 gold badge44 silver badges78 bronze badges
asked Oct 2, 2016 at 5:17

8 Answers 8

5

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.

answered Oct 2, 2016 at 5:31
Sign up to request clarification or add additional context in comments.

9 Comments

nice one! should it be return [item1, item2] to create the array as OP asked?
let me double check on that, looks like yes I had that in my fiddle, editing. Now it is updated. Looks like I had flip-flopped them. The working demo and answer should be correct.
@J.D.Pace interestingly, lodash will often outperform native .map, if you're curious. Great library, and even has a .flatten() function to avoid having to use that slick flattening trick - much easier to read. I recommend lodash for this kind of operation
@the_5imian I've looked at lodash for other projects. Perhaps it's time to revisit it. Check out the syntax in the answer describing the Cartesian Product.
@J.D.Pace basically how lodash will handle this. In addition, the functional manipulation of 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.
|
0

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.

answered Oct 2, 2016 at 5:39

Comments

0

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));

answered Oct 2, 2016 at 5:28

Comments

0

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; }

answered Oct 2, 2016 at 7:21

4 Comments

One tiny issue here. If your array items are arrays, then .concat() will unwrap them. ie try [['a', 'b', 'c'], ['1', '2', '3', '4'], [['A'], ['B']]]. I have the same problem in this one could you give a hand..?
@redu, in the above case, you could wrap the item in brackets, before concatinating them, like var p = part.concat([a]); maybe this helps in your problem as well (concar reduces only one level of the array).
Are you arrayifying all items there? Yes.. That's something that i'have thought but seemed to me like a dirty hack. Hmm no.. on a second thought in your case it's doable i suppose.
it's just a workaround for the automatic rest operator of concat. right, it looks a bit hacky.
0

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));

answered Oct 2, 2016 at 8:36

Comments

-1

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);
answered Oct 2, 2016 at 5:26

1 Comment

Could you at least explain why it's 'off'? That's what the question is really asking.
-1

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]));
answered Oct 2, 2016 at 5:51

4 Comments

Walk me through the => syntax, please.
@J.D.Pace It's an arrow function, in ES6.
This is just using map as an iterator over an external array. Not very elegant.
@Redu -- as I said, it would be better if the language had a flatMap(), or if you add one (using Underscore or the like).
-1

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);
 }
}
answered Oct 2, 2016 at 5:39

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.