Well, in the last posts we covered how to write a range generator function using for-loop and recursion. While iterative algorithms are more efficient because they do not create additional stack frames and pass data across function calls, however, in these times of functional programming, imperative programming is getting less popular.
So how do we create iterative and yet not recursive algorithm to generate range() in JavaScript?
Array method and spread magic
function generate(size) {
// Create an empty array
let empty = new Array(size);
// It contains 5 empty slots
console.log(empty);
// Now create a filled array
// Use empty array's offset
let filled = [...empty.keys()];
// [0, 1, 2, 3, 4 ]
console.log(filled);
}
generate(5);
Output?
empty [ <5 empty items> ]
filled [ 0, 1, 2, 3, 4 ]
Hey! We have already generated a range(5) of 0..4. Why not take this further and make it flexible?
We are getting closer
function generate(size, skip=1) {
// Create an empty array
let empty = new Array(size);
// It contains 5 empty slots
console.log("empty", empty);
// Now create a filled array
// Use empty array's offset
let filled = [...empty.keys()];
// [0, 1, 2, 3, 4 ]
console.log("filled", filled);
// Range with skip
let ready = [...empty.keys()]
.map(value => value * skip);
console.log("ready", ready);
}
generate(5, 2);
Output
empty [ <5 empty items> ]
filled [ 0, 1, 2, 3, 4 ]
ready [ 0, 2, 4, 6, 8 ]
So now we can generate an array of specific size, fill it with values and those values can come from an expression like value * skip
We are ready!
function* range(start, end, skip) {
// Sanity checks
if ((start == undefined) ||
(start != undefined &&
end == undefined &&
skip != undefined) ||
(start < end && skip <= 0) ||
start > end && skip >= 0)
return null;
// Check for situations like
// range(5) and make it range(0, 5)
end == undefined &&
([end, start] = [start, 0]);
// If skip is undefined
// set it to +1 or -1 depending on range
!skip && (skip = (start < end) ? +1 : -1);
yield* [...
[...Array(Math.abs(end - start)
/ Math.abs(skip)).keys()]
.map(value => start + value * skip)
];
}
Woah! What happened!!!
Nothing much.. just two things
1. Generate an array of given size e.g.
range(5) -> array of 5 (0, 1, 2, 3, 4)
range(0, 3) -> array of 3 (0, 1, 2)
range(0, 6, 2) -> array of 3 (0, 2, 4) we are skipping 2!
// Generate array of start to end / skip
[...Array(Math.abs(end - start) / Math.abs(skip)).keys()]
But the above array is empty, so we are using keys()
, which will only give us 0, 1, 2, 3…
It won’t apply skip if its other than 1.
2. Fill the array with values
range(0, 6, 2) should generate (0, 2, 4)
Ascending with skip
// generate(3, 8, 2) -> (3, 5, 7)
.map(value => start + value * skip)
// 0 => 3 + 0 * 2 == 3
// 1 => 3 + 1 * 2 == 5
// 2 => 3 + 2 * 2 == 7
Descending
// generate(6, 0, -2) -> (6, 4, 2)
.map(value => start + value * skip)
// 0 => 6 + 0 * -2 == 6
// 1 => 6 + 1 * -2 == 4
// 2 => 6 + 2 * -2 == 2
.map() is so sweet! It transform our offsets into final values.
// Yield values from the array
// Where were generate array with keys
// Then map the offsets into range values
yield* [...
[...Array(Math.abs(end - start)
/ Math.abs(skip)).keys()]
.map(value => start + value * skip)
];
So there we have it. An iterative range generator function which does not us any form of imperative statements like if and for and is more efficient than a recursive range generator.
If you know another way of generating range function, do post a comment!