4

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.

HynekS
  • 2,738
  • 1
  • 19
  • 34
  • 7
    ES6 is not a replacement for ES5, if a standard for loop makes sense, use a standard for loop. – Keith Nov 09 '18 at 14:01
  • @Keith I agree, i am just curious if it's possible. – HynekS Nov 09 '18 at 14:01
  • 2
    You're looking for the `zip` function (https://stackoverflow.com/questions/4856717/javascript-equivalent-of-pythons-zip-function), then `firstCol = zip(...grid)[0]` – georg Nov 09 '18 at 14:02
  • If you just want to console log each entry, then -> `for (let i of board) { for (let j of i) console.log(j); }` – Keith Nov 09 '18 at 14:04
  • @Keith Yes, I use it to iterate over *rows* , but I need to iterate over *columns*. – HynekS Nov 09 '18 at 14:06

6 Answers6

4

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.

georg
  • 211,518
  • 52
  • 313
  • 390
  • Seems very interesting. I have to confess I have no clue why `*it` is yielded as a generator, but I will take a look at Kyle Simpsons 'Async and performance' and hopefully find an answer there… – HynekS Nov 09 '18 at 14:26
  • Is it possible to pass a callback (`console.log()` is enough) at the end of a column? – HynekS Nov 09 '18 at 15:00
  • 1
    @HynekS: if you need to do something for each column, it might be easier to use zip without chain and iterate them separately, `for(col of zip(board)) { for(item of col)... ` – georg Nov 09 '18 at 15:21
2

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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thank you. Not exactly what I was looking for (a unicorn?), but very interesting, inspiring and probably useful. – HynekS Nov 09 '18 at 14:12
2

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);
brk
  • 48,835
  • 10
  • 56
  • 78
2

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);
}
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
1

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.

Vignesh Raja
  • 7,927
  • 1
  • 33
  • 42
0

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]);
  }
}
ksav
  • 20,015
  • 6
  • 46
  • 66