It is reasonably common to need an array populated by a simple sequence of numbers, usually to manipulate them later. Uses include:
- Generating page numbers (
<a href="page1.html">1</a>) - Retries (n attempts to login before being locked out)
- Mathematics (any ranged sum or product, combinatorics, Taylor/Maclaurin series, partial harmonic series, Fourier series...)
Currently these can be created imperatively via for loop:
function range(start, stop, step){
let arr = [];
for(let i = 0; i < start+stop; i++){
arr[i] = start + (i * step);
}
return arr;
}
(Of course, one could elect to make any manipulations they wanted inside the for loop, rather than generating the array and then manipulating it.)
One can also create such an array more functionally using array methods in various ways:
const range = (start, stop, step) => Array((stop - start) / step)
.fill(0)
.map((_,i) => start + (i * step));
// or
const range = (start, stop, step) => Array(stop - start)
.fill(0)
.map((_,i) => start + i)
.filter(v => (v-start) % step === 0)
The for loop solution has detractors simply due to its use of for loops, but in any case it uses a fair amount of code for a fairly simple concept.
The same problem applies to the Array method approach -- an array created via Array(n) cannot be meaningfully mapped over until it is filled with something either via for loop or with Array.prototype.fill(), which can only fill with a constant value. Meaning a third step (at least) is required to generate the final array, and at least two passes over the entire array.
Additionally, the Array methods approach has a lot of potential pitfalls (there are issues with both of my demo implementations, edge cases where they don't work, etc).
I think it would be useful to have a static method on Array, tentatively called Array.range, with the following semantics:
-
Array.range(stop: number)-- for any positivestopreturn an array of integers in the interval[0, Math.ceil(stop) - 1]inclusive, ordered from least to greatest. For any negativestopreturn an array of integers in the interval[Math.floor(stop) + 1, 0]inclusive, tentatively ordered from greatest to least (though I'd be open to arguments either way on that). Forstop === 0, return[], or possibly[0], it's up for discussion. -
Array.range(start: number, stop: number, step = 1)-- Ifstartis less thanstop, return an array of the set of alln_k = start + k*Math.abs(step)wherekis a non-negative integer andn_kis in the interval[start, stop)(note that it is inclusive on the low end, exclusive on the high end a laArray.prototype.slice()). Ifstartis greater thanstop, returns an array of the set of alln_k = start - k * Math.abs(step)wherekis a non-negative integer andn_kis in the interval[start, stop)with the same restrictions as the prior case. Throw an error (RangeError?) onstep === 0. Either throw an error, or return[]or[start]onstart === stop. - Possibly?
Array.range(start: number, stop: number, step: number, modifier: (value: number index: number)=> number)with the same logic as the prior case, but the functionmodifieris mapped across the resultant array in the same pass as creation (reducing the need to then immediately call.map()on the array, which would also introduce an additional pass over the array)
I think that having this baked into the Array class would allow for some optimizations at the interpreter level that would be more difficult or impossible to do in a library, such as making the creation slightly more lazy in some cases.
Is this (or a version of this) something that anyone else would support?