4

I am trying to make a function in javascript that returns an array from range(start,end) and im supposed to make an optional argument that defaults to one when it is undefined. I can get the function to work when I provide all the arguments but returns an empty array when I only pass two arguments. Here is the question:

Write a range function that takes two arguments, start and end, and returns an array containing all the numbers from start up to (and including) end.

Next, write a sum function that takes an array of numbers and returns the sum of these numbers. Run the previous program and see whether it does indeed return 55.

As a bonus assignment, modify your range function to take an optional third argument that indicates the “step” value used to build up the array. If no step is given, the array elements go up by increments of one, corresponding to the old behavior. The function call range(1, 10, 2) should return [1, 3, 5, 7, 9]. Make sure it also works with negative step values so that range(5, 2, -1) produces [5, 4, 3, 2].

And here is my code:

function range(start, end, increment){
var array = [];
var current = start;
var counter;
if (increment == undefined){
    counter = 1;
}

else {
    counter = increment;
}

if (increment > 0){
    while(current <= end){
        array.push(current);
        current += counter;
    }
}
  
else if (increment < 0){
    while(current >= end){
        array.push(current);
        current += counter;
        
    }
}

return array;
}

can someone explain why its breaking? I know some c# and I used to being able to jump into the debugger in visual studio when something goes wrong unlike javascript.

peterh
  • 11,875
  • 18
  • 85
  • 108
  • 1
    JS has plenty of debugger as well. The one in Chrome is probably the best. Pop it open you'll be able to step through the code. – Evan Trimboli Jun 01 '16 at 12:31
  • The common JS idiom for *if undefined, be this instead* is: `var counter = increment || 1;` – 4castle Jun 01 '16 at 12:35
  • @4castle could you explain further? not really sure how that works. would it be better to use the `?` operator? –  Jun 01 '16 at 12:37
  • @Nate See [this question](http://stackoverflow.com/q/2100758/5743988), it explains it nicely. If the value on the left is *falsey* (aka `undefined`, `null`, `0`, `""`, `false` or `NaN`) then it will assign the value on the right. – 4castle Jun 01 '16 at 12:38
  • Related to https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp – aloisdg Dec 03 '22 at 15:44

9 Answers9

6

A very simple unidirectional (ascending), inclusive range – goes from x to y incrementing by 1 each time.

// range :: (Int, Int) -> [Int]
const range = (x,y) =>
  x > y ? [] : [x, ...range(x + 1, y)];

console.log(range(1,4)); // [1,2,3,4]
console.log(range(3,3)); // [3]
console.log(range(6,3)); // []

A slight adaptation that supports bidirectional (ascending or descending) range – still increments or decrements by 1

// range :: (Int, Int) -> [Int]
const range = (x,y) => {
  if (x > y)
    return range(y,x).reverse();
  else
    return x === y ? [y] : [x, ...range(x + 1, y)];
}

console.log(range(1,4)); // [1,2,3,4]
console.log(range(3,3)); // [3]
console.log(range(6,3)); // [6,5,4,3]

Another adaptation that uses higher-order functions for more control over the range – this effectively gives you the stepping/incrementing behaviour some of you are looking for – tho this is more powerful because it lets you use a function, t, to choose the next value.

const gte = x => y => y >= x;
const lte = x => y => y <= x;
const add = x => y => y + x;
const sub = x => y => y - x;

// range :: (Int, (Int -> Bool), (Int -> Int)) -> [Int]
const range = (x, p, t) => {
  if (p(x))
    return [x, ...range(t(x), p, t)];
  else
    return [];
};

console.log(range(2, lte(8), add(2))); // [2,4,6,8]
console.log(range(9, gte(0), sub(3))); // [9,6,3,0]
console.log(range(9, gte(0), sub(5))); // [9, 4]

// very power. wow.
const double = x => x + x;
console.log(range(2, lte(50), double)); // [2,4,8,16,32]

This function has the same risks inherent with for and while – it's up to you to make sure you don't put it into an infinite loop.


functional overload

Warning: Esoteric, impractical functionals ahead. The following information is provided for your academic pleasure only.

The range function also happens to be one of my favourite demonstrations of the Y combinator. I'll show you two examples here.

naïve range

const U = f => f (f);
const Y = U (h => f => f (x => h (h) (f) (x)));

const range = Y (f => acc => x => y =>
  x > y ? acc : f ([...acc, x]) (x + 1) (y)
) ([]);

console.log(range (3) (6)); // [3,4,5,6]
console.log(range (6) (6)); // [6]
console.log(range (9) (6)); // []

and the higher-order range

const U = f => f (f);
const Y = U (h => f => f (x => h (h) (f) (x)));

const lt = x => y => y < x;
const gt = x => y => y > x;
const add1 = x => x + 1;
const sub1 = x => x - 1;

const range = Y (f => acc => x => p => t =>
  p(x) ? f ([...acc, x]) (t(x)) (p) (t) : acc 
) ([]);

console.log(range (3) (lt(6)) (add1)); // [3,4,5]
console.log(range (6) (lt(6)) (add1)); // []
console.log(range (9) (gt(6)) (sub1)); // [9,8,7]

What a thing of beauty that is.

Mulan
  • 129,518
  • 31
  • 228
  • 259
5

You could simplify the code a bit and use the increment variable for incrementing. But before, I suggest to test if the value is falsy (0, null, undefined, etc) and assign then 1 to it.

Not implemented: check if start and end is appropriate.

function range(start, end, increment) {
    var array = [];
    var current = start;

    increment = increment || 1;
    if (increment > 0) {
        while (current <= end) {
            array.push(current);
            current += increment;
        }
    } else {
        while (current >= end) {
            array.push(current);
            current += increment;
        }
    }
    return array;
}

console.log(range(1, 3, 0));    
console.log(range(2, 5));
console.log(range(1, 9, 1));
console.log(range(5, 2, -1));    
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 1
    This is nice, because it also prevents an infinite loop from when the 3rd parameter is `0`. – 4castle Jun 01 '16 at 12:45
  • 1
    @4castle I would argue otherwise. It's not a utility's purpose to save you from yourself. There's nothing preventing you from putting `do`, `for`, or `while` into an infinite loop. Why should functions we write be any different? I actually prefer APIs with hard edges. I feel behaviour we didn't specify should be *undefined*. See my answer for clarification of my point. – Mulan Sep 30 '16 at 18:33
  • @NinaScholz, all that aside, this is otherwise a very nice answer ^_^ – Mulan Sep 30 '16 at 18:36
  • @naomik It's called defensive programming. I agree though, it would be better if it just returned `undefined` when `increment === 0`, or maybe an empty array. – 4castle Oct 02 '16 at 22:57
1

First you check if increment is undefined and set counter accordingly, but later you check if (increment > 0){ again. While it is undefined none of your cases matches, so nothing happens.

Change your checks to this:

if (counter > 0){
  // ...
}
else if (counter < 0){
  // ...
}
Gerald Schneider
  • 17,416
  • 9
  • 60
  • 78
1

Very compact range function that handles float and negative numbers:

const range = (lower,upper,step)=>{
  return Array.from(new Array(Math.floor(upper/step-lower/step)+1),(_,i)=>lower/step+i).map(x=>x*step)
}

For example you can use it like:

range(10,30,3) // [10, 13, 16, 19, 22, 25, 28]
range(0,0.5,0.01) // [0, 0.01, 0.02, ... , 0.48, 0.49, 0.5]
range(1,10,2) // [1, 3, 5, 7, 9]
range(-5,10,0.5) // [-5, -4.5, -4, ... , 1, 1.5, 2]
range(5,2,-0.5) // [5, 4.5, 4, 3.5, 3, 2.5, 2]

Here a more understandable version:

const range = (lower, upper, step) => {
  end = upper / step // Upper bound
  start = lower / step // Lower bound
  n = Math.floor(end - start) + 1 // Size that includes upper bound as well
  zeros_arr = Array(n).fill(0) // Init array with n zeros
  unscaled_arr = zeros_arr.map((_, i) => (start + i)) // Setting each zero to the upper bound + the index
  range_arr = unscaled_arr.map(x => (x * step)) // Scaling every numbers with the step
  return range_arr
}
MrRedbloX
  • 11
  • 2
0

The given answers are great. I just wanted to give you an idea of how a more functional approach could solve the task:

// auxiliary functions:
const append = (x, xs) => xs.concat([x]);
const prepend = (x, xs) => [x].concat(xs);

// main function
const range = (x, y, step, acc = [], op = append) =>
 step && step < 0
  ? range(y, x, -step, acc, prepend)
  : step && x <= y
   ? range(x + step, y, step, op(x, acc), op) // tail call
   : acc;

console.log(range(1,5,1)); // [1,2,3,4,5]
console.log(range(1,5,2)); // [1,3,5]
console.log(range(1,5,6)); // [1]
console.log(range(5,1,1)); // []
console.log(range(1,5,0)); // []
console.log(range(5,1,-1)); // [5,4,3,2,1]
console.log(range(5,1,-2)); // [5,3,1]
console.log(range(5,1,-6)); // [1]
console.log(range(1,5,-1)); // []

Algorithm:

  • acc = [] and op = append default parameter values (are taken if omitted during the function invocation)
  • step && step < 0 short circuits if step is zero, otherwise checks if step is negative
  • range(y, x, -step, acc, prepend) is called when step is negative and converts range's parameterization so that step can be positive (note that -step is equivalent with -(-1), which is evaluated to 1)
  • range(x + step, y, step, op(x, acc), op) recursive case that means, the function calls itself (notice that op can be either append or prepend depending on the initial sign of step, that x is increased by step and appended/prepended to acc)
  • acc base case that stops the recursion and returns the accumulated array
0

I solved this problem as part of the eloquent javascript course. Based on the problem statement I chose default parameters for the increments and used a while loop to include the second argument.


function range(start, stop, increment=1)
{
  let range_arr = [];
  if(start < stop)
  {
    while( start <= stop)
    {
      range_arr.push(start);
      start += increment;
    }
  }
  else{
    while( start >= stop)
    {
      range_arr.push(start);
      start += increment;
    }
  }
  return range_arr;
}

0

I decided to use the for loop to create the increments as follows.

function range(start,end) {
  let array = [];
  
  for (let counter = 0; counter < end; counter++) {
    array.push(start);
    start += 1;
  }
  return array;
  }
  console.log(range(1,10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
0

I've using the snippet for range, with help of iterative callback theory.

That repeat the same function function with respected range attribute i.e 10-100. also 10-100 with 2 or 5 different.

function range(n, m, callback, k){
 if(!k) k=1;
 while(true){
  if(n<=m){
   callback(n);
   n+=k;
   if(n>=m) break;
  }else{
   callback(n);
   n-=k
   if(n<=m) break;
  }
 }
}

you can execute the snippet by

range(10, 100,function(n){
 // do here
},10)

you can extends the snippets by

function range_arr(n, m,callback,k) {
 let a = []
 range(n,m,function(n){
 a.push(callback(n))
 },k)
 return a;
}

you can use this snippet by

let arr=range_arr(0,10,function(n){
  return n=n*n;
 },2);

(5)[0, 4, 16, 36, 64]

Marimuthu
  • 1
  • 1
0

I realize this is an extremely old question and you probably don't need it anymore, but just in case someone reads this, my favorite way to do this is using a generator:

function* rangeGenerator(start, end = null, step = 1) {
    if (end == null) {
        end = start
        start = 0
    }

    if (Math.sign(end - start) !== Math.sign(step)) {
        step *= -1
    }

    while (Math.sign(step) === 1 ? start < end : start > end) {
        yield start
        start += step
    }
}

const range = (start, end = null, step = 1) => [...rangeGenerator(start, end, step)]

This way, the range function will generate an array that goes from start (inclusive) to end (exclsive). step should always be provided as a positive number because the sign is automatically handled by the generator according to whiche of start and end is bigger. You can pass it a negative step, but it's unnecessary.

This gives you the ability to use a for of loop over the range

for (let i = 0; i < 100; i++) {}

// Is the same as
for (const i of range(100)) {}
Gustavo Shigueo
  • 399
  • 3
  • 11