Javascript Generate Multidimensional array of 2 sizes

Question

I cannot figure out the best way to dynamically generate a multidimensional array with 2 different sizes.

We have a UI that requires a row of 4 items, then 3. This pattern would repeat until the content in the array has been spent.

This is essentially what I need to do:

// Convert
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];

// to
const rows [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10, 11], [12, 13, 14]];

This is what I currently have, it is only converting the arrays to 4 each.

 const buildRows = (arr, length) => arr.reduce((rows, val, i) => (
  i % length == 0 ? rows.push([val]) : rows[rows.length-1].push(val)
) && rows, []);

Thank you in advance for the help.


Show source
| javascript   | arrays   | multidimensional-array   2017-05-11 23:05 8 Answers

Answers ( 8 )

  1. 2017-05-11 23:05

    It ain't pretty and as much as I try to write functional code, it's pretty easy with a while loop...

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    const newArray = [];
    
    let i = 0;
    while (i < array.length) {
      let four = array.slice(i, i + 4)
      if (!(four.length > 0)) {
        break;
      }
      newArray.push(four)
      i += 4;
      let three = array.slice(i, i + 3);
      if (!(three.length > 0)){
        break;
      }
      newArray.push(three);
      i += 3;
    }
    
    return newArray
    
  2. 2017-05-11 23:05

    You can achieve that using Array#reduce, a pointer to the last place, and a step variable that alternates between 3 and 4:

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
    
    let last = 0;
    let step = 0;
    
    const result = array.reduce((r, num, i) => {
      if(i === last + step) { // when the previous sub array is full
        r.push([]); // add another sub array
        
        last = i; // mark the start index of the current sub array
        
        step = step === 4 ? 3 : 4; // alternate the step
      }
      
      r[r.length - 1].push(num); // push the number to the last sub array
      
      return r;
    }, []);
    
    console.log(result);

  3. 2017-05-11 23:05

    The following solution will mutate (empty) the input array array:

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    
    let result = [], i = 0;
    while(array.length) {                          // while there still items in array (array is continually getting shrunk untill it is emptied (array.length === 0))
      result.push(array.splice(0, i++ % 2? 3: 4)); // cut the first 3 or 4 numbers depending on the index of the cut i (if i is pair, then cut 4, else, cut 3) and then push the cut-out items into the result array
    }
    
    console.log(result);

    If you don't want to mutate it, then use slice instead of splice, but you'll have to provide the start index of the cut:

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    
    let result = [], i = 0, next = 0;                      // next will be the index from which the cut will start
    while(next < array.length) {                           // there is still items to cut
      let itemsToCut = i % 2? 3: 4;                        // determine how many items we are going to cut
      result.push(array.slice(next, next + itemsToCut));   // cut the items between next and next + itemsToCut
      next += itemsToCut;                                  // increment next by the number of cut-out items so it will point to the next item
      i++;
    }
    
    console.log(result);

  4. 2017-05-11 23:05

    I suggest a more self-documenting generator solution where even & odd row-sizes are not hardcoded but supplied via arguments:

    function* reshape(array, ...rows) {
      let i = 0;
      while (true) for (let row of rows) {
        if (i >= array.length) return;
        yield array.slice(i, i += row);
      }
    }
    
    // Example:
    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    for (let row of reshape(array, 4, 3)) console.log(row);

    A true generator purist would simplify reshape by first introducing a repeat generator:

    function* repeat(iterable) {
      while (true) yield* iterable;
    }
    
    function* reshape(array, ...rows) {
      let i = 0;
      for (let row of repeat(rows)) {
        if (i >= array.length) break;
        yield array.slice(i, i += row);
      }
    }
    
    // Example: 
    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    for (let row of reshape(array, 4, 3)) console.log(row);

  5. 2017-05-11 23:05

    Straighfoward and easy-to-read solution:

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    
    const r = [];
    let chunk = [];
    let l = 4;
    
    array.forEach((el, i) => {
      if (chunk.length < l) chunk.push(el);
      if (chunk.length === l) {
        r.push(chunk); chunk = [];
        l = ( l === 4 ) ? 3 : 4;
      }
    })
    
    console.log(r)

  6. 2017-05-11 23:05

    Yet another solution. Clearly everyone is having a good time with this one.

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    
    function divide(array, idx, num, result)
    {
      if(array.length <= idx) return result;
      result.push(array.slice(idx,idx+num));
      return divide(array, idx+num, num === 4 ? 3 : 4, result);
    }
    
    
    console.log(divide(array, 0, 4, []));

  7. 2017-05-11 23:05

    We can think of it as slicing elements from the array in a loop. It's just that we need to alternate between 4 and 3 instead of a constant value to slice. We can parameterize alternating values by passing them in a function instead of hardcoding it in the solution like below:

    1. Use Array##slice and
    2. Just swap current and next like this by using destructuring assignment to achieve the solution.
    3. Sub array sizes(4,3) can be modified outside actual logic or can be passed in a function to have flexible solution.

    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
    
    function TransformArray(array, current, next) {
      let start = 0,
        ans = [];
    
      while (start < array.length - 1) {
        ans.push(array.slice(start, start + current));
        start += current;
        [current, next] = [next, current]; //swap the count of array size
      }
      return ans;
    }
    
    console.log(TransformArray(array, 4, 3));
    console.log(TransformArray(array, 3, 3));

  8. 2017-05-12 08:05

    Here's kind of a different way of doing this, I'm expanding a bit to allow you to arbitrarily pass array lengths, this allows the PM to change their mind any time and it isn't a big deal.

    This could be cleaned up a bit, I wanted to leave it more verbose to make it easier to read.

    // Setup the function getting in:
    //     an array
    //     first array's length
    //     second array's length
    const arrayParser = (inArr,arr1len,arr2len) => {
        // Create a new array.
        let outArr = [];
        // Basic forEach is basic, we need the value and the index.
        inArr.forEach((val,idx) => {
            // If the index's modulus of the total of the two array lengths is:
            //   0 OR the first array's length
            // Push a new empty array.
            if (idx%(arr1len+arr2len)===0 || idx%(arr1len+arr2len)===arr1len) {
                // Create a new array with the current value
                outArr.push([]);
            }
            // Push the value to the last array in the out multidimensional array
            outArr[outArr.length-1].push(val);
        });
        // You got it.. return the array.
        return outArr;
    };
    
    // Single Dimensional Array
    const singleArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30];
    // Parse it.
    // Expects:
    //        A Single Dimensional Array
    //        Length of the first array
    //        Length of the second array
    console.log(arrayParser(singleArray,10,4));
    console.log(arrayParser(singleArray,2,4));
    console.log(arrayParser(singleArray,3,4));
    console.log(arrayParser(singleArray,4,3));
    console.log(arrayParser(singleArray,1,2));
    

    This works, because you know the length of each of the inner arrays, so you don't really need to figure out anything.

    Here's a 4,3 set broken out.

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
    
    Push a new array at 0 and 4.
    
               4+3
    index     total   Modulus
     0     %    7    =   0 <-- push [], push 1
     1     %    7    =   1 <-- push 2
     2     %    7    =   2 <-- push 3
     3     %    7    =   3 <-- push 4
     4     %    7    =   4 <-- push [], push 5
     5     %    7    =   5 <-- push 6
     6     %    7    =   6 <-- push 7
     7     %    7    =   0 <-- push [], push 8
     8     %    7    =   1 <-- push 9
     9     %    7    =   2 <-- push 10
    10     %    7    =   3 <-- push 11
    11     %    7    =   4 <-- push [], push 12
    12     %    7    =   5 <-- push 13
    13     %    7    =   6 <-- push 14
    
    Returns
    
    [[1,2,3,4],[5,6,7],[8,9,10,11],[12,13,14]]
    
◀ Go back