141

What is the best way to do the below in more functional way (with ES6/ES7)

let cols = [];
for (let i =0; i <= 7; i++) {
   cols.push(i * i);
}
return cols;

I tried like,

return [ ...7 ].map(i => {
  return i * i;
});

but that translated to

[].concat(7).map(function (n) {
  return n * n;
});

which is not what I expected.

EDIT:

@pavlo. Indeed, that was a mistake. I was using JSX, and for example, I want 7 divs, (untested)

let cols = [];
    for (let i =0; i <= 7; i++) {
       cols.push(<div id={i}> ...  </div>)
    }
    return cols;

so the idea was indeed to reduce the number of temp variables and procedural feel.

raphinesse
  • 13,873
  • 4
  • 33
  • 44
bsr
  • 52,180
  • 78
  • 198
  • 296

3 Answers3

232

One can create an empty array, fill it (otherwise map will skip it) and then map indexes to values:

Array(8).fill(0).map((_, i) => i * i);
Pavlo
  • 35,298
  • 12
  • 72
  • 105
  • 10
    If don't need the result array from map, and you want to keep the 'for' word in there, you could use `Array(8).fill().forEach((_,i)=>console.log(i))` or `[...Array(8)].forEach((_,i)=>console.log(i))` – aljgom May 09 '19 at 23:25
  • 1
    Note that this code will fail in Typescript, unless you add `undefined` as a first argument to `fill`. According to [this github issue](https://github.com/microsoft/TypeScript/issues/25392#issuecomment-804255033) Typescript maintainers seem to be against this specific use of the `Array.prototype.fill`. – Adam Mar 31 '21 at 14:59
  • Thanks, Adam. For practical purposes, I've updated the answer to use `.fill(0)`. – Pavlo Mar 31 '21 at 15:21
  • In my case this answer help me a lot, thank you. – Caio Apr 09 '21 at 16:12
  • Or `Array.from({length: 8}, (_, i) => i * i)`. – Ludovic Kuty May 27 '21 at 08:31
78

ES7 Proposal

Warning: Unfortunately I believe most popular platforms have dropped support for comprehensions. See below for the well-supported ES6 method

You can always use something like:

[for (i of Array(7).keys()) i*i];

Running this code on Firefox:

[ 0, 1, 4, 9, 16, 25, 36 ]

This works on Firefox (it was a proposed ES7 feature), but it has been dropped from the spec. IIRC, Babel 5 with "experimental" enabled supports this.

This is your best bet as array-comprehension are used for just this purpose. You can even write a range function to go along with this:

var range = (u, l = 0) => [ for( i of Array(u - l).keys() ) i + l ]

Then you can do:

[for (i of range(5)) i*i] // 0, 1, 4, 9, 16, 25
[for (i of range(5,3)) i*i] // 9, 16, 25

ES6

A nice way to do this any of:

[...Array(7).keys()].map(i => i * i);
Array(7).fill().map((_,i) => i*i);
[...Array(7)].map((_,i) => i*i);

This will output:

[ 0, 1, 4, 9, 16, 25, 36 ]

Downgoat
  • 11,422
  • 5
  • 37
  • 64
  • 1
    @vihan1086: array comprehensions were proposed for ES many times, but didn't make it in the spec. `.map(i => i*i)` was deemed to be so much simpler :-) – Bergi Jun 04 '15 at 18:38
  • This didn't work when I tried it: Array(7).keys().map(i => i * i); The keys() returned an iterator. This works however: Array.from(Array(7).keys()).map(i => i * i) notice the need to explicitly load the iterator into another array. – arcseldon Dec 09 '15 at 11:51
  • @arcseldon No it does not work. I'm not sure what I was thinking when I posted :p Will fix – Downgoat Dec 09 '15 at 16:16
  • @Bergi They missed the ES6 spec but they are in the ES7, if you use a transpiler such as babel or target Firefox, you usually can set an "experimental" option to enable time – Downgoat Jan 04 '16 at 04:28
  • `[...Array(7)]` works just fine without the `.keys()` part. – slikts Feb 02 '16 at 19:04
  • 2
    Array comprehension is not part of ES7. It's not even a proposal anymore as far as I can tell. – Felix Kling Mar 05 '16 at 02:31
  • @FelixKling no but I think babel with experimental turned on supports it so I'll leave it. – Downgoat Mar 05 '16 at 02:32
  • 2
    At least don't claim they are in ES7. That will only confuse people. I don't think Babel supports them anymore: https://babeljs.io/docs/plugins/ . – Felix Kling Mar 05 '16 at 02:32
  • @FelixKling iirc, they were proposed for ES7. I will update – Downgoat Mar 05 '16 at 02:40
  • Is there any way to get a sense of the space and time efficiency of this? Say, on the magnitude of `1e6`? Or do we just have to experiment? – BaseZen Feb 16 '19 at 01:41
  • @BaseZen can't say these would on the surface be efficient given that we would be constructing a non-holey array of a huge size with something like `1e6`. JS engine might do under the hood optimization but I don't have any specifics if that happens – Downgoat Feb 16 '19 at 01:54
25

Here's an approach using generators:

function* square(n) {
    for (var i = 0; i < n; i++ ) yield i*i;
}

Then you can write

console.log(...square(7));

Another idea is:

[...Array(5)].map((_, i) => i*i)

Array(5) creates an unfilled five-element array. That's how Array works when given a single argument. We use the spread operator to create an array with five undefined elements. That we can then map. See http://ariya.ofilabs.com/2013/07/sequences-using-javascript-array.html.

Alternatively, we could write

Array.from(Array(5)).map((_, i) => i*i)

or, we could take advantage of the second argument to Array#from to skip the map and write

Array.from(Array(5), (_, i) => i*i)

A horrible hack which I saw recently, which I do not recommend you use, is

[...1e4+''].map((_, i) => i*i)
Paul
  • 130,653
  • 24
  • 259
  • 248