Ranges are natively supported by a few (popular) programming languages. They allow for iteration over a defined space, while not have a linear increase in their memory footprint (all ranges always store a similar amount of data).
Let's try adding a similar idiom to JavaScript!
One way to approach this challenge is to write a plugin for a transpiler (for example a babel plugin) that would allow the following syntax:
const range = (0..5)
for (let i of range){
console.log(i)
// 0, 1, 2, 3, 4
}
Instead, we will provide a similar functionality with vanilla JavaScript.
for (let i of range(0, 5)) {
console.log(i)
// 0, 1, 2, 3, 4
}
We does the range stop at
4
instead of5
? This is a design choice, and as most languages that have a built-inrange
as not inclusive of their last value, therange
utility that we will build will follow a similar convention by default.
The above syntax also allows us to pass a third argument to the function to control the step
in between each iteration:
for (let i of range(0, 10, 2)) {
console.log(i)
// 0, 2, 4, 6, 8
}
To start, let's create a class Range
which will host the data needed for a range:
class Range {
constructor(start, stop, step = 1) {
this._start = Number(start);
this._stop = Number(stop);
this._step = Number(step);
// Initialise a counter for iteration
this.i = Number(start);
}
first() {
return this._start;
}
last() {
return this._stop;
}
step() {
return this._step;
}
}
We can now create a very basic (and not very useful) range:
const range = new Range(0, 10);
range.first(); // 0
range.last(); // 10
range.step(); // 1 (by default)
One of the main reasons we want ranges though is to iterate over them ... so let's implement iteration protocols in our Range
class!
To do so, we need to implement a next()
method, as well as a [Symbol.iterator]
method.
class Range {
constructor(start, stop, step = 1) {
...
// Initialise a counter for iteration
this.i = Number(start);
}
first() { ... }
last() { ... }
step() { ... }
next() {
if (this.i < this._stop) {
const value = this.i;
this.i += this._step;
return { value, done: false };
}
return { value: undefined, done: true };
}
[Symbol.iterator]() {
return this;
}
}
Great! Now we can use our ranges as follow:
const range = new Range(0, 5)
for(let i of range) {
console.log(i)
// 0, 1, 2, 3, 4
}
or
const range = new Range(0, 5)
range.next() // { value: 0, done: false }
range.next() // { value: 1, done: false }
range.next() // { value: 2, done: false }
range.next() // { value: 3, done: false }
range.next() // { value: 4, done: false }
range.next() // { value: undefined, done: true }
There is one issue with our current implementation though, and that is that the range is depleted after a single iteration. We cannot reuse the same range in multiple consecutive loops.
Luckily, there is a one line fix to support that:
class Range {
constructor(start, stop, step = 1) {
...
// Initialise a counter for iteration
this.i = Number(start);
}
first() { ... }
last() { ... }
step() { ... }
next() {
if (this.i < this._stop) {
const value = this.i;
this.i += this._step;
return { value, done: false };
}
// We reset the value once we have iterated over all values so that
// ranges are reusable.
this.i = this._start;
return { value: undefined, done: true };
}
[Symbol.iterator]() {
return this;
}
}
Finally, to achieve the semantics we defined at the beginning, we need to wrap our class creation in a function:
class Range { ... }
function range(start, stop, step = 1) {
return new Range(start, stop, step);
}
for (let i of range(0, 5)) {
console.log(i)
// 0, 1, 2, 3, 4
}
Again, inspired by this blog post, I decided to build a library with the aforementioned features and much more! Check it out:
AntonioVdlC / range
⛰ - Implement ranges in JavaScript
range
Implement ranges in JavaScript.
Installation
This package is distributed via npm:
npm install @antoniovdlc/range
Motivation
Ranges are natively supported by a few (popular) programming languages. They allow for iteration over a defined space, while not having a linear increase in their memory footprint (all ranges always store a similar amount of data).
Usage
You can use this library either as an ES module or a CommonJS package:
import range from "@antoniovdlc/range";
- or -
const range = require("@antoniovdlc/range");
To create a range:
const start = 0;
const stop = 10;
const step = 2; // Defaults to `1` if not passed
const inclusive = true; // Defaults to `false` if not passed
const r = range(start, stop, step, inclusive);
You can also pass an options object for convenience:
const start = 0;
…
Top comments (0)