Array Number Ranges in JavaScript ES6

Warning:

Since this post was published, the ECMAScript Technical Committee has removed the comprehensions feature from ES6. This post shows usage of other (supported) features of the language, and has been left unedited.

I’ve spent the last few days really digging into ECMAScript 6. The sheer scope of it compared to ES5 is amazing, and has me all excited for the possibilities the web has available for us in the very near future! If you see yourself programming JavaScript at any point, it’s really worth it to learn the new features, which (although complex at first glance) really make JavaScript a lot more intuitive both by borrowing great ideas from other languages (like iterators, generators, classes, block scoping, comprehensions, the list goes on!) and by making existing features less verbose and easier to read (as with destructuring, default parameters, rest & spread, classes, enhanced object literals, and arrow functions). Definitely go check it out; you can start using it today!

As I was playing around writing ES6 code, I needed an array to iterate over that had numbers in ascending order, and I wondered if ES6 had some new construct for that. In Python it’s as simple as writing:

array = range(1,100)

to get an array (or “list” in Python) from [1, 100). Even though it turns out ES6 has no such range() function, that’s okay, because we now have another Python-inspired trick up our sleeve—array comprehensions! :D

Looking around on the web for somebody who knew more about this than me, I found Ariya Hidayat’s thought-provoking post on using array compehensions generically to create sequences of not just numbers, but anything, really. There he creates a string of the alphabet using comprehensions:

[for (i of Array.apply(0, Array(26)).map((x, y) => y)) String.fromCharCode(65 + i)].join('');
// => "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

I liked his clever use of Array.apply() along with ES5 and ES6 stuff like .map() and arrow functions. I even learned something new about creating arrays that have holes using Array(num) syntax. Even though I was intrigued, I still felt there had to be a way to get a simple number range with a shorter syntax. I found out about ES6’s Array.prototype.fill() method and using Ariya’s techniques, came up with this:

[for (j of Array(100).fill(0).map((v,i) => i)) j]
// => [0, 1, 2, ..., 99]

A little bit shorter, but still kind of a roundabout way of just getting some numbers. After some more playing around, I finally arrived at this:

[for (i of Array(100).keys()) i]
// => [0, 1, 2, ..., 99]

Looks much simpler, right? This uses a new feature in the ES6 Array API, Array.prototype.keys(), which returns an iterator that when used with the for..of construct, will iterate over all the keys of an array, that is, all numbers from 0 to array.length-1. Pretty cool! Object.keys([1,2,3]) can do a similar thing, but won’t work if your array is dynamically created as with Array(10) because of the holes (bringing us back to square one). What’s nice about the trick shown above is that if you want to change your starting value or the interval between numbers, all you have to do is change the final i in the comprehension. You can also change the array’s length with the argument to Array():

[for (i of Array(97).keys()) i+3]
// => [3, 4, 5, ..., 99]

[for (i of Array(100).keys()) i*10]
// => [0, 10, 20, ..., 990]

[for (i of Array(100).keys()) i*10+10]
// => [10, 20, 30, ..., 990, 1000]

Still not quite as readable as Python’s range(), but workable. If you really just need numbers 0 to whatever, you can do something even simpler:

Array.from(Array(100).keys())
// => [0, 1, 2, ..., 99]

ES6 introduces Array.from(), which converts array-like objects to arrays. It’s an elegant way to, for example, convert function arguments or DOM NodeLists to an array, common needs in JavaScript:

/* _Working with NodeLists_ */

// The ES5 Way
var slice = [].slice;
var nodes = document.querySelectorAll('p');
var classes = slice.call(nodes).map(function (element) {
    return element.className;
});

// Da ES6 Way, homie!
var nodes = document.querySelectorAll('p');
var classes = Array.from(nodes).map(element => element.className);


/* _Getting function arguments_ */

// The ES5 Way
function doSomething () {
  var args = [].slice.call(arguments);
  ...
}

// Shiny ES6 Way
function doSomething () {
    var args = Array.from(arguments);
}

// Even easier would be to use rest parameters
function doSomething (...args) {}

So sweet! Anyway, I’m going off on a tangent about how awesome all of this is. I’ll leave you with one more trick for generating ranges. An up-to-infinite amount, actually (or at least till the bits overflow :). It’s a technique using generators that I learned from o.O who learned it from the MDN Wiki — thanks, y’all!

// create a generator function returning an
// iterator to a specified range of numbers
function* range (begin, end, interval = 1) {
    for (let i = begin; i < end; i += interval) {
        yield i;
    }
}

// try it out!

// storing into a local array
var seq = [for (i of range(20,50,5)) i];
// => [20, 25, 30, 35, 40, 45]

// using it on the fly without storing numbers in memory
for (i of range(1,7,2)) {
  console.log(i);
}
// => 1
// => 3
// => 5

// looking a lot like Python now :)
var iter = range(12,19,3);

var num;
num = iter.next(); // => { value: 12, done: false }
num = iter.next(); // => { value: 15, done: false }
num = iter.next(); // => { value: 18, done: false }
num = iter.next(); // => { value: undefined, done: true }
num = iter.next(); // => { value: undefined, done: true }

The beauty of this is in its simplicity and the fact that the generator uses next-to-no memory for storing numbers, as would a regular function that returns an array. No matter the size of the end value you pass in (even Infinity!), you won’t crash the user’s machine or slow down their experience because, unless you make one yourself, no gigantic array is made. The only caveat is that you must use this function either with iterators or a for..of loop, but with ES6, that’s standard fare (and fun!).

Comments