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 positivestop
return an array of integers in the interval[0, Math.ceil(stop) - 1]
inclusive, ordered from least to greatest. For any negativestop
return 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)
-- Ifstart
is less thanstop
, return an array of the set of alln_k = start + k*Math.abs(step)
wherek
is a non-negative integer andn_k
is in the interval[start, stop)
(note that it is inclusive on the low end, exclusive on the high end a laArray.prototype.slice()
). Ifstart
is greater thanstop
, returns an array of the set of alln_k = start - k * Math.abs(step)
wherek
is a non-negative integer andn_k
is 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 functionmodifier
is 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?