1

Say you have an array like this and I want to reorder it.

var myarray = [
    [178, 559, 906, 1252] , [381 , 537 , 937 , 1115] , [346 , 529 , 913 , 1069]
];

What I wanted to do was to loop through each of the arrays, take the 1st value of each array on the first loop and push each into a separate array. The second time the loop runs, take the 2nd value of each array, push those into the separate array and so on and so on. So that the separate array looks like this;

var myNewArray = [178, 381, 346, 559, 537, 529, 906, 937, 913, 1252, 1115, 1069];

I've got so far as to loop through each array and get all of the values, but can't work out the logic to target ONLY the 1st values on the first loop, the 2nd of the second loop etc etc

var arraylength = myarray.length;

for (var i = 0; i < arraylength; i++ ) {

    console.log(i+1 + " time around");

    var noc = myarray[i].length;

    for (var k = 0; k < noc; k++) {
        var a = myarray[i][k];
        console.log(a);
    };
};

Here's a JSFiddle

2
  • Are all arrays guaranteed to be the exact same length, all the time? Commented Mar 12, 2016 at 23:30
  • In this example they are, but no @Norguard, no guarantee that they will always be the same length Commented Mar 12, 2016 at 23:32

4 Answers 4

2

Okay...

Perhaps this is overboard, but I had a long trip home and some time to kill.

Your algorithm troubles revolve around the fact that you're missing a step. What you actually need to do is loop through the range of the longest array. Which means that you need to create a range (an actual range, or just know the min/max bounds of it) that goes from 0 to the max of all of the lengths of all of the arrays.

When you've done that, you need to go through that range, and within that range, you need to go through the list of all arrays (looping through each 2nd-dimension array, per iteration).
For each array, you check if it has an element at the current index.
If it does, add it to the new array.
The first step (the one you're missing) is almost like dealing cards; you have 4 people at the table, but it's actually the 52 cards that you're iterating through on the outside, not the 4 people.

This has a bunch of different names, depending on what you're doing.
This might be a zip a merge a rotation (though rotation doesn't really account for the flattening, just the shuffling).

So without further ado, here are 3 solutions, which are all different takes on this.
The first solution is the more-classical "JavaScript as Java" implementation:

function findMax (arrays) {
  var i = 0;
  var l = arrays.length;
  var max = 0;
  var array = [];

  for (; i < l; i += 1) {
    array = arrays[i];
    max = array.length > max ? array.length : max;
  }

  return max;
}


function rotateAndFlatten (arrays) {

  var flattenedArray = [];

  var maxLength = findMax(arrays);

  var inner = 0;
  var outer = 0;

  var array;
  var currentValue;

  for (; outer < maxLength; outer += 1) {
    for (inner = 0; inner < arrays.length; inner += 1) {
      array = arrays[inner];
      currentValue = array[outer];
      if (currentValue || currentValue === 0) {
        flattenedArray.push(currentValue);
      }
    }
  }

  return flattenedArray;
}



var inputArray = [ [1, 2, 3], [4, 5, 6, 7], [8, 9, 10] ];
var outputArray = rotateAndFlatten(inputArray);

document.querySelector(".ResultInput--ES3").textContent = JSON.stringify(inputArray);
document.querySelector(".ResultOutput--ES3").value = JSON.stringify(outputArray);
<div ><pre>Input: <code class="ResultInput ResultInput--ES3"></code></pre></div>
<div ><pre>Output: <code ><output class="ResultOutput ResultOutput--ES3"></output></code></pre></div>

The second is the ES5 way I'm more used to thinking, these days, with partially-applied functions, and working with sets of things one operation at a time, rather than instances of things with manual loop management:

function makeRange (min, max) {
  var range = [];
  var i = min;

  while (i < max) {
    range.push(i);
    i += 1;
  }

  return range;
}


function concat (a, b) {
  return a.concat(b);
}


function identity (x) {
  return x;
}


function max (a, b) {
  return b > a ? b : a;
}


function pluck (key) {
  return function pluckFrom (obj) {
    return obj[key];
  };
}


function fillIndexArrays (arrays) {
  return function (i) {
    return arrays.map(pluck(i));
  };
}


function rotateAndFlatten (array) {
  var getLength = pluck("length");
  var maxLength = array.map(getLength).reduce(max, 0);
  var indices = makeRange(0, maxLength);

  return indices.map(fillIndexArrays(array)).reduce(concat, []).filter(identity);
}


var inputArray = [ [1, 2, 3], [4, 5, 6, 7], [8, 9, 10] ];
var outputArray = rotateAndFlatten(inputArray);

document.querySelector(".ResultInput--ES5").textContent = JSON.stringify(inputArray);
document.querySelector(".ResultOutput--ES5").value = JSON.stringify(outputArray);
<div ><pre>Input: <code class="ResultInput ResultInput--ES5"></code></pre></div>
<div ><pre>Output: <code ><output class="ResultOutput ResultOutput--ES5"></output></code></pre></div>

And here's the ES6 version of that, which can now use generators and splat operators to greatly simplify the construction of a range, and use lambdas to where code might be compacted and be equally legible (to me/my team):

const max = (a, b) => b > a ? b : a;
const identity = x => x;
const concat = (a, b) => a.concat(b);

function * range (min, max) {
  let i = min;
  while (i <= max) {
    yield i;
    i += 1;
  }
};


const pluck = (key) => { return (obj) => obj[key]; };


function rotateAndFlatten (arrays) {
  const getLength = pluck("length");
  const maxLength = arrays.map(getLength).reduce(max, 0);

  const indices = [...range(0, maxLength)];

  return indices
    .map(i => arrays.map(pluck(i)))
    .reduce(concat, [])
    .filter(identity);
}


var inputArray = [ [1, 2, 3], [4, 5, 6, 7], [8, 9, 10] ];
var outputArray = rotateAndFlatten(inputArray);

document.querySelector(".ResultInput--ES6").textContent = JSON.stringify(inputArray);
document.querySelector(".ResultOutput--ES6").value = JSON.stringify(outputArray);
<div ><pre>Input: <code class="ResultInput ResultInput--ES6"></code></pre></div>
<div ><pre>Output: <code ><output class="ResultOutput ResultOutput--ES6"></output></code></pre></div>

As a bonus, here's how I might implement it if I were writing JS as though I were writing C code that makes me very sad when I have to debug a logic error (but cuts right to the quick of the algorithm):

function init (arrs) {
  var max;
  var i = 0;
  var l = arrs.length;
  var max = 0;
  for (i = 0; i < l; i++)
    if (max < arrs[i].length)
      max = arrs[i].length;

  var j = 0;
  var arr = [];
  for (i = 0; i < max; i++)
    for(j = 0; j < arrs.length; j++)
      if (arrs[j][i] !== undefined)
        arr.push(arrs[j][i]);

  document.querySelector(".ResultOutput--C").value = JSON.stringify(arr);
}

var arrs = [ [1, 2, 3], [4, 5, 6, 7], [8, 9, 10] ];
document.querySelector(".ResultInput--C").textContent = JSON.stringify(arrs);
init(arrs);
<div ><pre>Input: <code class="ResultInput ResultInput--C"></code></pre></div>
<div ><pre>Output: <code ><output class="ResultOutput ResultOutput--C"></output></code></pre></div>

Hope that gives you something to chew upon, and some insight into the low-level algorithms which might be at play, and the higher-level ways of implementing those lower-level algorithms.

Sign up to request clarification or add additional context in comments.

1 Comment

That must have been a long old journey home! Anyway, thanks so much for your more than detailed answer to my question. It's so helpful when someone actually tells you were your going wrong. There's definitely a lot to chew upon there so thanks again dude for a really helpful and insightful answer!
1

Alternatively you can do it like this: .shift() returns the first item of the array, but note that this solution will alter the original array

 var myarray = [
  [178, 559, 906, 1252],
  [381, 537, 937, 1115],
  [346, 529, 913, 1069]
];

var resultArray = [];
var maxlen = Math.max.apply(null, myarray.map(function(i) {
  return i.length; }));
var l = 0;
while (l < maxlen) {
  var s = 0;
  var a = [];
  for (i = 0; i < myarray.length; i++) {
    a.push(myarray[i].shift());
   }
  resultArray.push(a);
  l++;
  s++;  
} 
console.log(resultArray);

1 Comment

maxlen is max length of longest array inside the myarray, so the while loop will iterate for that number of times
1

Instead of iterating myarray in the outer loop, and each subarray in the inner one, you should do the opposite. Then it's a bit tricky, because the outer loop iterates the subarrays, but they could have different lengths. I added a done variable to know where to stop.

var myNewArray = [];
for(var i=0, done=false; !done; ++i) {
  done = true;
  for(var j=0; j<myarray.length; ++j) {
    if(i < myarray[j].length) {
      done = false;
      myNewArray.push(myarray[j][i]);
    }
  }
}

Comments

0

Lodash has a method called zip that does what you are trying to do.

console.log(_.zip(myarray)); // [[178, 381, 346], [559, 537, 529], [906, 937, 913], [1252, 1115, 1069]]

3 Comments

Hi @Rastalamm thanks for that, but that doesn't give me the array in the correct order, just gives me the same as what my code spits out
You need to flatten the "zip" array of arrays that the zip function gives you and it should be what you are looking for? (One long array?)
What do you mean flattern out the zip array or array? If I use the var myarray it doesn't work, it just gives me the normal array. If I put all of the array values into the function then it does work

Your Answer

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.