Over the weekend, I published a simple class for creating numeric ranges. It's called NumRange. It follows on from last week's post, but it's even better. Instead of creating entire arrays in memory, it returns an iterable NumRange object. When the object is iterated over, e.g. in a for...of loop, it yields the next value in the sequence from a generator function.

More accurately, the first time the generator function is called, it returns a Generator object. When a value is consumed by calling the Generator object's .next() method, the generator function executes until it encounters the yield keyword. This is repeated each time the .next() method is called until the end of the generator function is reached. All of this is done internally by the for...of loop, but it is possible to consume iterables directly.

The benefit of this is that it lets you perform operations on the NumRange object without having to create the entire array. For example, I've written an .at() method to get the value in the range at the given index, and an .includes() method to check if a value exists in the range. The NumRange object only keeps a reference to its .start, .stop, and .step properties; the methods compute their return values from the values of these properties.

const range = new NumRange({ start: 1, stop: 6 });

// I can call these methods without creating the full array...
range.at(0); // 1
range.includes(3); // true

// I can also iterate over the range, possibly breaking early...
for (const num of range) {
if (num === 3) break;

// I can use spread syntax too...
Math.max(...range); // 5

// And I can still create the full array if I want to...
const nums = Array.from(range); // [1, 2, 3, 4, 5]

I'll publish a detailed explanation next week, but many thanks to my colleague Simon for inspiring this post! He's not only a great developer in his own right, but also a brilliant mathematician and the creator of Mathematico. He pointed out to me that the range type in Python works in a similar way, prompting me to create my NumRange class. The following is taken directly from the Python docs:

The advantage of the range type over a regular list or tuple is that a range object will always take the same (small) amount of memory, no matter the size of the range it represents (as it only stores the start, stop and step values, calculating individual items and subranges as needed).

I'll be back next week with a detailed explanation of my NumRange class, iterators and generators, and iteration protocols!