Is it possible to iterate across nested arrays using for...of and/or for...in loop?

Viewed 143

Let's say I have a "rectangular grid" made of nested arrays, like this:

let board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4'],
];

I am trying to iterate across its columns, so the result will be like 'a0', 'b0', 'c0', 'd0', 'a1'... etc.

Of course I can do it using good old for loop:

const iterateAcrossColumnsES5 = () => {
  for (let i = 0; i < board[0].length; i++) {
    for (let j = 0; j < board.length; j++) {
      console.log(board[j][i]);
    }
  }
}

But I like to try make it more ES6 like terse and readable. I am trying to use for.. of and/or for.. in loops, but I got only as far as:

const iterateAcrossColumnsES6 = () => {
  for (let [i, item] of Object.entries(board)) {
    for(let row of board) {
      console.log(row[i])
    }
  }
}

But it is nor terse nor readable, and it kinda works only in case that board is a 'square' (the parent array length is the same as its childrens), otherwise I got either too much or not enough iterations.

It is possible to do it? I haven't try to use map() or forEach(), I'm O.K. with them, but I am curious if I can use only for..of or for..in.

6 Answers

Nothing built-in for that in js, but with two tiny helper functions you can write the loop in a quite elegant way:

function *chain(its) {
    for (let it of its)
        yield *it
}

function zip(arrays) {
    return arrays[0].map((e, i) => arrays.map(a => a[i]))
}

//

let board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4'],
]


console.log([...chain(board)].join(' '))


console.log([...chain(zip(board))].join(' '))

chain connects multiple iterable objects so that you can iterate them as one thing and zip takes an array of arrays and transposes it.

You could transpose the matrix and then iterate.

const transpose = (r, a) => a.map((v, i) => (r[i] || []).concat(v));
let board = [['a0', 'a1', 'a2', 'a3', 'a4'], ['b0', 'b1', 'b2', 'b3', 'b4'], ['c0', 'c1', 'c2', 'c3', 'c4'],  ['d0', 'd1', 'd2', 'd3', 'd4']];

for (let a of board.reduce(transpose, [])) {
    for (let v of a) {
        console.log(v);
    }
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use map to create an array of a0,b0.... and then further reduce it. Then use join with delimiter , to create the desired result

let board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4'],
];



var result = board.reduce((res, b) => res.map((elem, i) => elem + ',' + b[i])).join(',');
console.log(result);

You can change the board's iterator, and than use array spread or for...of to get the items:

const board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4'],
];

board[Symbol.iterator] = function() {
  const rows = board.length;
  const max = rows * board[0].length;
  let current = 0;
  return {
    next: () => ({
      value: this[current % rows][parseInt(current / rows)],
      done: current++ === max
    })
  };
};

console.log([...board]);

for(const item of board) {
  console.log(item);
}

Using for...in:

var board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4']
];

var result = [];

for (var i in board)
    for (var j in board[i])
        result[+j * board.length + +i] = board[i][j];
    
console.log(result);

It is not recommended to use for...in on arrays. MDN Docs for reference

Using for...of:

var board = [
  ['a0', 'a1', 'a2', 'a3', 'a4'],
  ['b0', 'b1', 'b2', 'b3', 'b4'],
  ['c0', 'c1', 'c2', 'c3', 'c4'],
  ['d0', 'd1', 'd2', 'd3', 'd4']
];

var result = [], i=0,j=0;

for (var arr of board) {
    for (var val of arr)
        result[j++ * board.length + i] = val;
    i++;j=0;
}

console.log(result);

In case the inner arrays are uneven in length, empty values will be present in the array. So need to filter those.

This will only serve your purpose if you have a square board.

let board = [
  ["a0", "a1", "a2", "a3", "a4"],
  ["b0", "b1", "b2", "b3", "b4"],
  ["c0", "c1", "c2", "c3", "c4"],
  ["d0", "d1", "d2", "d3", "d4"],
  ["e0", "e1", "e2", "e3", "e4"]
];


for (const i in board) {
  for (const j in board) {
    console.log(board[j][i]);
  }
}

Related