Building a range generator in JavaScript – Part III

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!

Range Generator Series

  1. Building a range generator in JavaScript – Part I (iterative – for loop)
  2. Building a range generator in JavaScript – Part II (recursive)
  3. Building a range generator in JavaScript – Part III (iterative – no for loop)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s