Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a linked list iterable in ES6

I have a linked list in JavaScript that I need to make iterable with a for of loop. I have almost done it but there seems to be no way to get the first value included in the result. Here is a simplified version:

var obj = {value: 1, next: {value: 2, next: {value: 3, next: {value: 4, next: {value: 5, next: {value: 6, next: {value:7, next: null}}}}}}};

obj[Symbol.iterator] = function() {
  var current = this;
  return {
    next() {
      if (current.next !== null) {
        current = current.next;
        return {value: current.value, done: false};
      }
      return {done: true}
    }
  }
}

for (const x of obj) {
  console.log(x)
}

// this is how you get the values printed with no loop
// console.log(obj.value + '->' + obj.next.value + '->' + obj.next.next.value)
like image 714
Didexe Avatar asked Feb 16 '17 23:02

Didexe


People also ask

How do you make an object iterable in JavaScript?

To make the range object iterable (and thus let for..of work) we need to add a method to the object named Symbol. iterator (a special built-in symbol just for that). When for..of starts, it calls that method once (or errors if not found). The method must return an iterator – an object with the method next .

When should we use generators in es6?

In a normal function, there is only one entry point: the invocation of the function itself. A generator allows you to pause the execution of a function and resume it later. Generators are useful when dealing with iterators and can simplify the asynchronous nature of Javascript.

What is iterator protocol?

In JavaScript an iterator is an object which defines a sequence and potentially a return value upon its termination. Specifically, an iterator is any object which implements the Iterator protocol by having a next() method that returns an object with two properties: value. The next value in the iteration sequence.


2 Answers

The problem is you're moving current to the next node before retrieving the value.

var obj = {value: 1, next: {value: 2, next: {value: 3, next: {value: 4, next: {value: 5, next: {value: 6, next: {value:7, next: null}}}}}}};

obj[Symbol.iterator] = function() {
  var current = this;
  return {
    next() {
      if (current) {
        var value = current.value;
        current = current.next;
        return {value: value, done: false};
      }
      return {done: true};
    }
  };
};

for (const x of obj) {
  console.log(x);
}

It's much easier to implement an iterator with a generator function.

var obj = {value: 1, next: {value: 2, next: {value: 3, next: {value: 4, next: {value: 5, next: {value: 6, next: {value:7, next: null}}}}}}};

obj[Symbol.iterator] = function*() {
  var current = this;
  while (current) {
    yield current.value;
    current = current.next;
  }
};

for (const x of obj) {
  console.log(x);
}
like image 59
4castle Avatar answered Sep 21 '22 11:09

4castle


You should be testing for current, not current.next:

obj[Symbol.iterator] = function() {
  var current = this;
  return {
    next() {
      if (current !== null) {
        var res = {value: current.value, done: false};
        current = current.next;
        return res;
      } else {
        return {done: true};
      }
    }
  };
}

But can write it much simpler as a generator method:

obj[Symbol.iterator] = function* () {
  for (var current = this; current !== null; current = current.next) {
    yield current.value;
  }
}

Btw, I would recommend not putting this iterator on every node of the list (or even on the first one). Put in on a separate object that points to the head of the list, or make it a static helper function:

let list = {
  head: obj, // could be null either
  *[Symbol.iterator]() {
    for (var current = this.head; current !== null; current = current.next) {
      yield current.value;
    }
  }
}

function* linkedList(head)
  for (; head !== null; head = head.next) {
    yield head.value;
  } 
}
like image 39
Bergi Avatar answered Sep 18 '22 11:09

Bergi