sammiepls
sammiepls

Reputation: 1633

How do you reset an iterator?

I'm learning about iterators/generators, and am wondering if there is a way to make the iterator reset itself?

For example, when after you call the last .next() is there a way to make it restart?

const arr = [ {name: "Bob"}, {name: "Peter"}, {name: "Sally"} ];

const names = nameGenerator(arr);

function nextName() {
  const current = names.next().value;
  const output = document.querySelector("output");
  if (current) { 
    output.innerText = current.name;
  } 
  else { 
    // code to reset it!
    output.innerText = 'Nothing left';
  }
}
  
function nameIterator(names) {
  let nextIndex = 0;
  return {
    next: function () {
      return nextIndex < names.length ? 
        { value: names[nextIndex++], done: false } :
        { done: true };
    }
  }
}

function* nameGenerator() {
  let index = 0;
  while (index < arr.length) {
    yield arr[index++];
  }
}

document.querySelector("button").addEventListener("click", nextName)
<button>Iterate me!</button>
<output></output>

In this snippet, when you click the button, it calls my iterator which will display the names in my names array one by one each time you click. How can I make it reset on the last click so it goes back to the first name?

So the display would be like:

Bob
Peter
Sally
Bob // get Bob again

I have also written the iterator using a generator, is there anyway for that to also be able to reset back to the first one after yielding through the array?

Upvotes: 4

Views: 3672

Answers (5)

trincot
trincot

Reputation: 351039

You can send something to your generator while consuming it, so it can act differently depending on that value, and perform an action like resetting.

In your example you want to cycle back at the end, but if in general you have another condition whereby you want to reset the iterator somewhere half-way, then using the argument of the iterator next method could be useful.

To demonstrate this, I have extended your snippet with a second button, with which the user can choose to reset the iterator at any time. It will also cycle if the user just keeps clicking the Next button. Note how yield returns the value that you pass to the next call:

const arr = [ {name: "Alice"}, {name: "Bob"}, {name: "Carol"}, {name: "Danny"} ];

const names = nameGenerator(arr);

function nextName(reset=false) {
    // Pass a boolean to the iterator's next method:
    const current = names.next(reset).value;
    const output = document.querySelector("output")
    output.innerText = current.name;
}
  
function* nameGenerator(names) {
    // Here we make an infinite sequence by wrapping the index around
    for (let i = 0; true; i = (i + 1) % names.length) {
        // Get the value that the consumer passed to `next`:
        const reset = yield names[i];
        // Did they want to reset?
        if (reset) i = -1; // The loop's increment will make it 0
    }
}

// Get the two buttons and pass a boolean to the nextName function
const [btnNext, btnFirst] = document.querySelectorAll("button");
btnNext.addEventListener("click", () => nextName(false));
btnFirst.addEventListener("click", () => nextName(true));
<button>Get Next</button>
<button>Get First</button>
<output></output>

Upvotes: 0

Avik Samaddar
Avik Samaddar

Reputation: 431

I came to the thread to look for the answer myself, and here's what I came up with:

class CyclicIter {

  constructor(arr) {
      this.arr = arr
      this._at = -1
    }
    [Symbol.iterator]() {
      return {
        next: () => {
          if (this._at < this.arr.length) {
            return {
              value: this.arr[this._at++],
              done: false
            }
          } else {
            this.reset()
            return {
              done: true
            }
          }
        }
      };
    };
  reset() {
    this._at = -1
  }
};

const myArray = [1, 2, 3, 4]
const myArrayIterable = new CyclicIter(myArray)
console.log(...myArrayIterable); // can be called multiple times

this example may be trivial since I am super new to JS. Any suggestions are appreciated.

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386766

You could change the index and take the remainder value, if the index is greater than the array index.

function nameIterator(names) {
    let nextIndex = 0;
    return {
        next: function() {
            nextIndex++;
            nextIndex %= name.length;
            return { value: names[nextIndex], done: false };
        }
    }
}

Upvotes: 1

Madara&#39;s Ghost
Madara&#39;s Ghost

Reputation: 175048

The general answer is that you can't reset an iterable in JS. This is, as far as I know, by-design, although I'm not privy to the decision-making process that was used to reach that conclusion.

In your specific case however, you don't want to necessarily reset the iterator, you want to cycle through the array. In that case, you can use the modulo operator as follows:

function nameIterator(names) {
  let nextIndex = 0;
  return {
    next: function() {
      nextIndex = (nextIndex + 1) % names.length;
      return { value: names[nextIndex], done: false };
    }
  }
}

Upvotes: 5

kockburn
kockburn

Reputation: 17636

To solve this, when the iterator is done reset it like so:

// code to reset it!
output.innerText = 'Nothing left'
names = nameGenerator(arr);

Unless someone proves me wrong, there is currently no method to reset an iterator that I'm aware of.

Working snippet example:

const arr = [ {name: "Bob"}, {name: "Peter"}, {name: "Sally"} ];

let names = nameGenerator(arr);


function nextName() {
   const current = names.next().value
  const output = document.getElementById("output")
   if (current) { 
      output.innerText = current.name
    } 
    else { 
    // code to reset it!
      output.innerText = 'Nothing left'
      names = nameGenerator(arr);
    }
}
  
function nameIterator(names) {
  let nextIndex = 0;
  return {
    next: function() {
      return nextIndex < names.length ? 
        { value: names[nextIndex++], done: false } :
        { done: true }
    }
  }
}

function* nameGenerator() {
  let index = 0;
  while(index < arr.length) {
    yield(arr[index++])
  }
}

 document.querySelector("button").addEventListener("click", nextName)
<button id="button">click me</button>
<p id="output"></p>

Upvotes: 1

Related Questions