How to generate range of numbers from 0 to n in ES2015 only?
Asked Answered
F

17

199

I have always found the range function missing from JavaScript as it is available in python and others? Is there any concise way to generate range of numbers in ES2015 ?

EDIT: MY question is different from the mentioned duplicate as it is specific to ES2015 and not ECMASCRIPT-5. Also I need the range to be starting from 0 and not specific starting number (though it would be good if that is there)

Forefend answered 29/4, 2016 at 21:39 Comment(5)
The answer is the same for ES5 and ES6.Gauger
But you can always use some og the new concepts such as generators, new array methods etc. in ES2015. That gives you extra set of tools to achieve the taskForefend
I think @Delapouite has the perfect answer to this in comments to an answer to the duplicated question: [...Array(n).keys()].Carn
related: Is there a mechanism to loop x times in ES6 without mutable variables? and functional way to iterate over range in ES6Stefaniestefano
[...Array(5)].map((_,i) => i+1)Yuk
L
364

You can use the spread operator on the keys of a freshly created array.

[...Array(n).keys()]

or

Array.from(Array(n).keys())

The Array.from() syntax is necessary if working with TypeScript

Logos answered 30/4, 2016 at 9:43 Comment(7)
Sweet: function range (start, end) { return [...Array(1+end-start).keys()].map(v => start+v) }Amalekite
This doesn't work in typescript because keys() returns an Array Iterator instead of an Array. Checkout aditya-singh's answer for a more universal approach.Ingles
…… or Array.from(Array(n).keys()).Vespine
@DavidGonzalezShannon Do you know why [...Array(n).keys()] doesn't work in Typescript? Is it an intentional deviation from other JS implementations?Roxieroxine
Hey @StuCox I have no idea why but it transpiles it to Array(5).keys().slice() and slice is not a method of array iterator. Here's an example of it not working typescriptlang.org/play/…Ingles
@DavidDomingo add the downlevelIteration flag or change your target to es6 or later.Leukocyte
Perf test here: jsbench.me/qvk74npuvm/1 Dr Axel's thoughts on array perf here: 2ality.com/2018/12/… :PMencius
F
124

I also found one more intuitive way using Array.from:

const range = n => Array.from({length: n}, (value, key) => key)

Now this range function will return all the numbers starting from 0 to n-1

A modified version of the range to support start and end is:

const range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);

EDIT As suggested by @marco6, you can put this as a static method if it suits your use case

Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);

and use it as

Array.range(3, 9)
Forefend answered 1/5, 2016 at 6:54 Comment(4)
Nice one! Why don't we extend Array static interface with it? In typescript works great with: interface ArrayConstructor { range(n: number): number[]; } Array.range = n => Array.from({length: n}, (value, key) => key); And then everywhere Array.range(x)...Gradey
[ts] Property 'range' does not exist on type 'ArrayConstructor'. thouths?Tartan
Overriding built-ins is considered bad practice in javascript now.Milena
Stick to range as util function or define your own object (with a type if using TS) and call it ArrayV2 or something. Don't mess with built in prototypes, it can backfire horribly.Sexed
B
54

With Delta/Step

smallest and one-liner

[...Array(N)].map((_, i) => from + i * step);

Examples and other alternatives

[...Array(10)].map((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array.from(Array(10)).map((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array.from(Array(10).keys()).map(i => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

[...Array(10).keys()].map(i => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]

Array(10).fill(0).map((_, i) => 4 + i * 2);
//=> [4, 6, 8, 10, 12, 14, 16, 18, 20, 22]

Array(10).fill().map((_, i) => 4 + i * -2);
//=> [4, 2, 0, -2, -4, -6, -8, -10, -12, -14]

Range Function

const range = (from, to, step) =>
  [...Array(Math.floor((to - from) / step) + 1)].map((_, i) => from + i * step);

range(0, 9, 2);
//=> [0, 2, 4, 6, 8]

As Iterators

class Range {
  constructor(total = 0, step = 1, from = 0) {
    this[Symbol.iterator] = function* () {
      for (let i = 0; i < total; yield from + i++ * step) {}
    };
  }
}

[...new Range(5)]; // Five Elements
//=> [0, 1, 2, 3, 4]
[...new Range(5, 2)]; // Five Elements With Step 2
//=> [0, 2, 4, 6, 8]
[...new Range(5, -2, 10)]; // Five Elements With Step -2 From 10
//=>[10, 8, 6, 4, 2]
[...new Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]

// Also works with for..of loop
for (i of new Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2

As Generators Only

const Range = function* (total = 0, step = 1, from = 0) {
  for (let i = 0; i < total; yield from + i++ * step) {}
};

Array.from(Range(5, -2, -10));
//=> [-10, -12, -14, -16, -18]

[...Range(5, -2, -10)]; // Five Elements With Step -2 From -10
//=> [-10, -12, -14, -16, -18]

// Also works with for..of loop
for (i of Range(5, -2, 10)) console.log(i);
// 10 8 6 4 2

// Lazy loaded way
const number0toInf = Range(Infinity);
number0toInf.next().value;
//=> 0
number0toInf.next().value;
//=> 1
// ...

From-To with steps/delta

using iterators

class Range2 {
  constructor(to = 0, step = 1, from = 0) {
    this[Symbol.iterator] = function* () {
      let i = 0,
        length = Math.floor((to - from) / step) + 1;
      while (i < length) yield from + i++ * step;
    };
  }
}
[...new Range2(5)]; // First 5 Whole Numbers
//=> [0, 1, 2, 3, 4, 5]

[...new Range2(5, 2)]; // From 0 to 5 with step 2
//=> [0, 2, 4]

[...new Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]

using Generators

const Range2 = function* (to = 0, step = 1, from = 0) {
  let i = 0,
    length = Math.floor((to - from) / step) + 1;
  while (i < length) yield from + i++ * step;
};

[...Range2(5, -2, 10)]; // From 10 to 5 with step -2
//=> [10, 8, 6]

let even4to10 = Range2(10, 2, 4);
even4to10.next().value;
//=> 4
even4to10.next().value;
//=> 6
even4to10.next().value;
//=> 8
even4to10.next().value;
//=> 10
even4to10.next().value;
//=> undefined
Byrnie answered 30/3, 2018 at 15:46 Comment(1)
Your updated TypeScript version doesn't work. It creates an empty array with the indicated size. You need to use Array.from with Array.keys with TypeScript. Array.from(Array(~~((to - from) / step) + 1).keys())Ingles
D
30

For numbers 0 to 5

[...Array(5).keys()];
=> [0, 1, 2, 3, 4]
Dartmoor answered 3/10, 2018 at 9:11 Comment(0)
L
16

A lot of these solutions build on instantiating real Array objects, which can get the job done for a lot of cases but can't support cases like range(Infinity). You could use a simple generator to avoid these problems and support infinite sequences:

function* range( start, end, step = 1 ){
  if( end === undefined ) [end, start] = [start, 0];
  for( let n = start; n < end; n += step ) yield n;
}

Examples:

Array.from(range(10));     // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
Array.from(range(10, 20)); // [ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]

i = range(10, Infinity);
i.next(); // { value: 10, done: false }
i.next(); // { value: 11, done: false }
i.next(); // { value: 12, done: false }
i.next(); // { value: 13, done: false }
i.next(); // { value: 14, done: false }
Lippi answered 27/9, 2018 at 20:42 Comment(0)
S
14

So, in this case, it would be nice if Number object would behave like an Array object with the spread operator.

For instance Array object used with the spread operator:

let foo = [0,1,2,3];
console.log(...foo) // returns 0 1 2 3

It works like this because Array object has a built-in iterator.
In our case, we need a Number object to have a similar functionality:

[...3] //should return [0,1,2,3]

To do that we can simply create Number iterator for that purpose.

Number.prototype[Symbol.iterator] = function *() {
   for(let i = 0; i <= this; i++)
       yield i;
}

Now it is possible to create ranges from 0 to N with the spread operator.

[...N] // now returns 0 ... N array

http://jsfiddle.net/01e4xdv5/4/

Cheers.

Sorrento answered 30/6, 2018 at 11:22 Comment(1)
Just because it is possible it doesn't mean you should. Messing with prototypes is bad practice, rather create a new proto if you have, not use built in protos for that.Sexed
R
8

Range with step ES6, that works similar to python list(range(start, stop[, step])):

const range = (start, stop, step = 1) => {
  return [...Array(stop - start).keys()]
    .filter(i => !(i % Math.round(step)))
    .map(v => start + v)
}

Examples:

range(0, 8) // [0, 1, 2, 3, 4, 5, 6, 7]
range(4, 9) // [4, 5, 6, 7, 8]
range(4, 9, 2) // [4, 6, 8] 
range(4, 9, 3) // [4, 7]
Rottenstone answered 19/11, 2019 at 19:30 Comment(1)
Nice add to the question! This helped me out to get much cleaner code in my Angular 8 html *ngFor loop templates.Thrift
M
5

You can use a generator function, which creates the range lazily only when needed:

function* range(x, y) {
  while (true) {
    if (x <= y)
      yield x++;

    else
      return null;
  }
}

const infiniteRange = x =>
  range(x, Infinity);
  
console.log(
  Array.from(range(1, 10)) // [1,2,3,4,5,6,7,8,9,10]
);

console.log(
  infiniteRange(1000000).next()
);

You can use a higher order generator function to map over the range generator:

function* range(x, y) {
  while (true) {
    if (x <= y)
      yield x++;

    else
      return null;
  }
}

const genMap = f => gx => function* (...args) {
  for (const x of gx(...args))
    yield f(x);
};

const dbl = n => n * 2;

console.log(
  Array.from(
    genMap(dbl) (range) (1, 10)) // [2,4,6,8,10,12,14,16,18,20]
);

If you are fearless you can even generalize the generator approach to address a much wider range (pun intended):

const rangeBy = (p, f) => function* rangeBy(x) {
  while (true) {
    if (p(x)) {
      yield x;
      x = f(x);
    }

    else
      return null;
  }
};

const lte = y => x => x <= y;

const inc = n => n + 1;

const dbl = n => n * 2;

console.log(
  Array.from(rangeBy(lte(10), inc) (1)) // [1,2,3,4,5,6,7,8,9,10]
);

console.log(
  Array.from(rangeBy(lte(256), dbl) (2)) // [2,4,8,16,32,64,128,256]
);

Keep in mind that generators/iterators are inherently stateful that is, there is an implicit state change with each invocation of next. State is a mixed blessing.

Manumit answered 30/11, 2018 at 14:57 Comment(0)
S
2

To support delta

const range = (start, end, delta) => {
  return Array.from(
    {length: (end - start) / delta}, (v, k) => (k * delta) + start
  )
};
Schnurr answered 19/7, 2017 at 16:18 Comment(0)
S
2

How about just mapping ....

Array(n).map((value, index) ....) is 80% of the way there. But for some odd reason it does not work. But there is a workaround.

Array(n).map((v,i) => i) // does not work
Array(n).fill().map((v,i) => i) // does dork

For a range

Array(end-start+1).fill().map((v,i) => i + start) // gives you a range

Odd, these two iterators return the same result: Array(end-start+1).entries() and Array(end-start+1).fill().entries()

Sam answered 7/8, 2020 at 16:55 Comment(0)
W
1

You can also do it with a one liner with step support like this one:

((from, to, step) => ((add, arr, v) => add(arr, v, add))((arr, v, add) => v < to ? add(arr.concat([v]), v + step, add) : arr, [], from))(0, 10, 1)

The result is [0, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9].

Whereabouts answered 7/9, 2017 at 1:22 Comment(2)
Is this the Y-combinator?Koel
It follows the idea of Y-combinator.Benzoin
T
1

This function will return an integer sequence.

const integerRange = (start, end, n = start, arr = []) =>
  (n === end) ? [...arr, n]
    : integerRange(start, end, start < end ? n + 1 : n - 1, [...arr, n]);

$> integerRange(1, 1)
<- Array [ 1 ]

$> integerRange(1, 3)
<- Array(3) [ 1, 2, 3 ]

$> integerRange(3, -3)
<- Array(7) [ 3, 2, 1, 0, -1, -2, -3 ]
Trioxide answered 6/4, 2019 at 16:55 Comment(0)
R
1

Few more ways to do

// Using `repeat` and `map`
const gen = n => [...'.'.repeat(n)].map((_,i) => i);

console.log('gen ', gen(5));


// Using `repeat` and `split`
const gen2 = n => ' '.repeat(n).split('').map((_,i) => i);

console.log('gen2 ', gen2(5));

// Using `concat` with recursive approach
const gen3 = n => n ? gen3(n-1).concat(n-1) : [];

console.log('gen3 ', gen3(5));


const range = (start, end, step = 1) =>
  start > end ? [] : [start].concat(range(start + step, end, step));

console.log('range', range(2, 10,2));
Revet answered 31/12, 2021 at 7:6 Comment(0)
S
0
const keys = Array(n).keys();
[...Array.from(keys)].forEach(callback);

in Typescript

Speech answered 15/8, 2017 at 7:37 Comment(3)
There's no reason to use both Array.from and spread syntax. And then it's exactly the same as the existing answer.Stefaniestefano
Just wanna point out [...Array(n).keys()] does not work in Typescript.Speech
Then use Array.from(Array(n).keys()). I'm pretty sure it should work though, what does the literal with spread syntax transpile to?Stefaniestefano
R
0

Here's another variation that doesn't use Array.

let range = (n, l=[], delta=1) => {
  if (n < 0) { 
    return l 
  }
  else {
    l.unshift(n)
    return range(n - delta, l) 
  }
}
Rippy answered 24/1, 2018 at 18:42 Comment(0)
S
0

Simple and concise use of iterators.

const range = (i, n) => ({
    [Symbol.iterator]: () => ({ 
        next: () => ({ done: i > n, value: i++  }) 
    })
})

// example usage
console.log(...range(1, 1000))
Suppressive answered 15/2 at 20:21 Comment(0)
J
-1

Generators now allow you to generate the number sequence lazily and using less memory for large ranges.

While the question specifically states ES2015, I expect a lot of Typescript users will end up here and the conversion to ES is straightforward...

function range(end: number): IterableIterator<number>;
// tslint:disable-next-line:unified-signatures
function range(begin: number, end: number): IterableIterator<number>;

function *range(begin: number, end: number = NaN): IterableIterator<number> {
    let num = 0;
    if (isNaN(end)) {
        end = begin;
    } else {
        num = begin;
    }
    while (num < end) {
        yield num++;
    }
}

The first two function declarations are just to provide more informative completion suggestions in your IDE.

Joeljoela answered 28/10, 2019 at 7:57 Comment(1)
Aaaaand you can tell I didn't read all the existing answers before posting :-/Joeljoela

© 2022 - 2024 — McMap. All rights reserved.