Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 4, 2022 01:36 am GMT

Building Iterators

As programmers, one of the very first thing we learn is "the loop". There is always an array or list or collection which needs to be looped through, an object or map or dictionary whose keys and values require traversing. Iterations are key programming concept.

Arrays and Maps are collections of things and it should to be defined explicitly prior to iterating. You can start out with an empty array or a declaration and you can push items to it. Example:

const things = ['headphone', 'keyboard', 'mouse']const friends = {    tomo: 'nuxt expert',    jt: 'writes nasty sqls',    deco: 'the leader',    van: 'php guru'} things.push('cables')// one of the ways of iterating arraysfor (const thing of things) {  console.log(thing)}// iterate the key of objectsfor (const name in friends) {  console.log(`${name} - ${friends[name]}`)}

There are many ways of iterating over an array or an object. To name a few:

  • for(const i = 0; i < arr.length; i+=1)
  • for...of MDN
  • for...in MDN
  • while() MDN
  • Array.forEach MDN
  • Array.map MDN
  • Array.filter MDN
  • Array.reduce MDN

One thing about arrays or objects(Map, Set etc) is that you kind of know what you are getting. You could push things into an array but you know what you pushed. Its not dynamic. If something is in array, its there for good until you remove it. Also, its taking up that space in the memory.

Iterator protocol

What if you had a dynamic array that calculated what value you get in the next iteration? What if that worked based on a formula that you've built? You need to use iterator pattern. You'll notice that its very simple to implement. Its a protocol that is well known among JS programmers and also followed in other languages too. An iterator is an object that has next() method. Calling next() function on the object gives us the iterator result which is an object with two properties - done which is a boolean to hold the status of the iterator and value to hold whatever you want to return. Let's build a simple range iterator. This range iterator will allow us to create a range of numbers by providing a start, end and step.

// iterator protocol: an agreed interfacefunction numberRangeIterator(start, end, step) {  let index = start  return {    next() {      if (index > end) {        return { done: true, value: 'thanks for using me' } // value is optional here but you can use it to return meta info      }      const value = index      index += step      return { done: false, value }    }  }}const iterator = numberRangeIterator(3, 30, 3)let iteratorResult = iterator.next()while (!iteratorResult.done) {  console.log(iteratorResult.value)  iteratorResult = iterator.next()}

You see? Its very simple and yet powerful. Two things to note:

  • the next function should return and object with done: true to indicate that there are no more elements. But it's not mandatory, you can have an iterator that runs forever!
  • you can have done: false or return just {value} and above code will just work fine.
function randomNumberIterator() {  return {    next() {      return { done: false, value: Math.random() }    }  }}const rIterator = randomNumberIterator()let rIteratorResult = rIterator.next()while (!rIteratorResult.done) {  console.log(rIteratorResult.value)  rIteratorResult = rIterator.next()}

While I cannot think of when you'd use the iterator above, I just wanted to demonstrate an iterator that can generate random numbers infinitely.

Iterable protocol

Iterable protocol goes one step further by defining a standard within JS language for any object to return an iterator. An iterable is an object that implements an iterator method called [Symbol.iterator]. The best thing about using iterables over the iterator which we talked about above is that it allows us to use JS native apis for looping over the array such as for...of. Let's build our numberRangeIterator as an iterable.

class NumberRange {  constructor(start, end, step) {    this.start = start    this.end = end    this.step = step  }  // for an object/class to classify as iterable  // it has to implement [Symbol.iterator]  [Symbol.iterator]() {    let index = this.start    return {      next: () => {        if (index > this.end) {          return { done: true }        }        const value = index        index += this.step        return { value }      }    }  }}const myRange = new NumberRange(3, 30, 3)for (const num of myRange) {  console.log(num)}

It was almost the same amount of code to define the iterable class and we reused most of our code. However, the beauty is in the way we consume the iterator. Using for...of makes it look so clean and concise. I prefer this over the while loop above. But it doesn't stop here. There are other ways you can consume this iterable. You can use it with spread operator.

const myRange2 = new NumberRange(5, 20, 4)console.log(...myRange2) // prints 5 9 13 17

Or, destructure and assign it

const myRange2 = new NumberRange(5, 20, 4)const [first, second, third] = myRange2console.log(first, second, third) // prints 5 9 13

There are other JS built-in APIs that accept iterables where you can pass your iterables such as Array.from(iterable), Set([iterable]), Promise.all(iterable) and even stream.Readable.from(iterable).

Read more about iterators here. You can pretty much treat it like your regular array but dynamic in nature and it will calculate your values only when it needs to. Things get a bit hairy though, when you start getting into the territory of async iterators but that is for another day.


Original Link: https://dev.to/dnafication/building-iterators-1ebb

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To