range
is a function that basically takes in a starting index and ending index then return a list of all integers from start to end.
The most obvious way would be using a for loop.
function range(start, end) {
var ans = [];
for (let i = start; i <= end; i++) {
ans.push(i);
}
return ans;
}
As a fan of FP, let's come up with an recursive solution. So the base case is obviously when the start and end are the same, the answer would simply be [start]
.
function range(start, end) {
if(start === end) return [start];
// recursive case
}
Now take the leap of faith, assume that range(start, end)
will just work. Then how do we solve the problem range(start, end)
? Simple! Just do [start, ...range(start + 1, end)]
.
So combining both, we get
function range(start, end) {
if(start === end) return [start];
return [start, ...range(start + 1, end)];
}
A lot more elegant than the for-loop solution in my opinion. But we could even go further if we use new Array(n)
which creates an array with n elements.
If we have an n element list, we could build a range from it by mapping each element to its index, i.e. arr.map((_, i) => i)
.
However, according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description, map
will not call for unassigned element. This mean we need to initialise the new Array(n)
before mapping. One standard technique is to use fill
. The final result is the following.
function range(start, end) {
return (new Array(end - start + 1)).fill(undefined).map((_, i) => i + start);
}
We could also make use of Array.from
to create range:
function range(start, end) {
return Array.from({ length: end - start + 1 }, (_, i) => i)
}
Thank you Step for mentioning about efficiency when handling large ranges, which essentially build a huge array. We could have a more efficient way of doing this by using generators.
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
We could use this generator in a for...of
loop (which would be very efficient) or use an array spread to retrieve all values (note that this essentially builds the array which is essentially the same as the non-generator approaches.)
for (i of range(1, 5)) {
console.log(i);
}
/* Output
* 1 2 3 4 5 */
[...range(1, 5)] // [1, 2, 3, 4, 5]
Since I always try to avoid for loops, we could also define the generator recursively as follows.
function* range(start, end) {
yield start;
if (start === end) return;
yield* range(start + 1, end);
}
Could you think of some cooler method to achieve this?
Top comments (59)
I like this way:
As array from works with arrayLike structures and receives a second optional argument mapFn, it's a nice candidate to build range.
Hi All, I'm trying to grasp this function:
But I still confused on why does passing { length } as first argument in Array.from is work to create new array with specific length.
Because what I read in the documentation about Array.from is only works for iterable or array like object.
Any explanation or help would be appreciate a lot. Thank you.
2 years too late to explain this. Hope it's not too late!
From the doc:
obj = { length: 10 }
is:length
propertyobj[i]
isundefined
for any integeri
.This is why
{ length }
count as an "array-like" object. You could write in the long form:But it's not worth listing those entries out coz they are by default
undefined
anyways.Yes, excellent solution.
This, slimmed down, version also works...
Almost.
Try:
as satisfying as this is, you're exposing a third argument that anyone can override. In a modern tech stack, this code is hesitantly admissible only if you can guarantee that absolutely no one can set
length
to be anything butend - start
, otherwise you need to invest in a much more complex testing framework, likely outweighing the expense of adding a second line to this function.const range = (start, end) => Array.from({length: end}, (_, i) => start + 1);
Sorry kerafyrm02, but that does not produce a range.
If I run range with range(1,50) I get [2,2,2,2,2,2,2,2,...]
const range = (start, end) => Array.from({length: end}, (_, i) => start + 1); console.log(range(0,20))
let r = (s, e) => Array.from('x'.repeat(e - s), (_, i) => s + i);
Ok here ya go... even shorter. :D
Rock, paper, scissors... 68 chars, you've nailed it!
lol., made
range
tor
.. so even shorter :DTrust me to bump into a js golfer.
Next you'll be telling me that you're dropping the let keyword and just leaving r on the global object!!
haha! nice solution John!
Well, thank you so much for saying!
This is how I went about creating a different solution although it is only really beneficial by calling (value in target) and really only counts as a "typeof range" :) Just bored and playing around with proxies.
I liked your recursive generator. How about something like this:
This is great, I wish this would make its way into the spec.
Haha! I like this idea!
If you're going to use => might as well go all in... (line break for readability on dev.to)
const range = (start, end) => new Array(end - start + 1)
.fill(undefined).map((_, i) => i + start)
Also, I'm a big fan of using variable names that describe what they are (even for internal functions), so (line break for readability on dev.to)
const range = (start, end) => new Array(end - start + 1)
.fill(undefined).map((value, index) => index + start)
On the other hand, I am a big fan of Haskell. And we tend to use
_
to refer to variables which we don't use in the function. But I do agree with you that having meaningful names is a good practice and could help readability.Even in Haskell, _ is a bad idea :) Think about the poor intern trying to learn the language while debugging your code. (And it isn't just Haskell...I did a lot of Perl back in the day, which at it best can look like line noise).
BTW since you are a fan of Haskell, I ran across this article the other day that you might enjoy, describing how to structure JS function with Haskell-styl currying.
Edit: Many years later, I use arrow functions whereever possible.
I don't like ambiguity in code. When I'm reviewing code and have to stop and figure out what this is referring to, it makes me frown; with arrow functions I always know exactly what this is. The 'swap this and that' pattern in JS always struck me as a hack. One of the big wins of arrow functions is eliminating the this ambiguity.
I have a similar feeling for hoisting. I understand why it is part of the language, but it always feels like a path to ruin when I have to rely on some behind-the-scenes reshuffling by the interpreter to get my code to run.
All that said, you are correct that there are situations in which arrow functions are not appropriate, which also make me crazy. Having two ways to write functions just adds confusion. "We recommend using arrow functions everyhwere. Oh, sorry, was that a constructor? Write that one this way instead..."
I can't wait to start seeing code in review that's built with templated classes. Maybe ES7 will introduce header files...
Haha! I totally understand what you mean. But still since ES6 introduces
class
we should really move away from usingfunction
as a constructor. As soon as it is consistent in a team then it's fine.I just use a one-liner recursive arrow function in ES6; note, it doesn't check but you should only pass it integers else it will never get to a == b so it will blow the stack!
Are you actually using this in production code? 😂😂
You realise how inefficient this is right? 😂😂
I have adapted Namir's method (see comments). And it is probably the most efficient.
I'm a little late for the discussion 😅 but I found the different ways interesting, I ended up getting this one. Is it valid?
Looks great to me!
This is an Awesome string of code you've written. I'm partial to Python3 behavior of range though. Made a few minor adjustments yours. Better to steal like an artist then build from scratch.
The recursive one is really nice. Can be shortened to:
Also this is another approach, + easier to handle descending sequences too:
Why not get really wild and crazy :P ?
1st define a unfoldr
then define a range in terms of unfold
excellent article btw, keep them coming :)
Might be inefficient to create an array for large ranges, and only supports range of integers (no dates). Have you seen Ruby's Range class? ruby-doc.org/core-2.2.0/Range.html
You could still implement toArray() and offer a covers() function.
Yes! You are right! I will add a generator implementation as well which should solve this problem! ;D
I'd like to see the generator implementation!
What do you think about it? I have implemented.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.