Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate over a Set or Map in reverse order in javascript?

I'm looking for a a way to iterate over a Set or Map in reverse order.

Consider this simple example in regular order:

var mySet = new Set([1,2,3,4,5]);
for(let myNum of mySet) {
  console.log(myNum); // output: 1, 2, 3, 4, 5 in sepearte lines
}

The iterator given from Set.prototype.values() or Set.prototype.entries() are also from start to beginning.

What would be the solution to iterate the Set (or Map) in the reverse order?

like image 849
Holger Stitz Avatar asked Jan 13 '17 12:01

Holger Stitz


2 Answers

There is no way to obtain a reversed iterator on Maps or Sets, as I have found out while trying to get the last item added to a Set. So the only way really is to use an intermediate Array and reverse it, like this:

var mySet = new Set([1,2,3,4,5]);
for (let myNum of Array.from(mySet).reverse()) {
  console.log(myNum);
}

Or you can use this alternate doubly linked Set implementation:

class LinkedSetLink {
  constructor(value) {
    this.value = value;
    this.prev = this;
    this.next = this;
  }
  
  insertBefore(item) {
    const prev = item.prev = this.prev;
    const next = item.next = this;
    next.prev = item;
    prev.next = item;
  }
  
  remove() {
    const prev = this.prev;
    const next = this.next;
    next.prev = prev;
    prev.next = next;
  }
}


class LinkedSet {
  constructor(iterable) {
    this._map = new Map();
    this._pivot = new LinkedSetLink(/* undefined */);
    if (iterable) {
      this._addAll(iterable);
    }
  }

  _addAll(iterable) {
    for (const item of iterable) {
      this.add(item);
    }
  }

  has(item) {
    return this._map.has(item);
  }

  add(item) {
    if (!this._map.has(item)) {
      const link = new LinkedSetLink(item);
      this._pivot.insertBefore(link);
      this._map.set(item, link);
    }
  }

  delete(item) {
    const link = this._map.get(item);
    if (link) {
      this._map.delete(item);
      link.remove();
    }
  }

  clear() {
    this._map.clear();
    this._pivot.next = this._pivot.prev = this._pivot;
  }

  get size() {
    return this._map.size;
  }

  values() {
    return this._map.keys();
  }

  keys() {
    return this.values();
  }

  [Symbol.iterator]() {
    return this.values();
  }

  *entries() {
    for (const key of this.values()) {
      yield [key, key];
    }
  }

  *reversedItems() {
    let link = this._pivot.prev;
    while (link !== this._pivot) {
      yield link.value;
      link = link.prev;
    }
  }

  first() {
    return this._pivot.next.value;
  }

  last() {
    return this._pivot.prev.value;
  }
}



const myset = new LinkedSet([1,2,3,4,5]);
for (let item of myset.reversedItems()) {
  console.log(item);
}
like image 146
Tamas Hegedus Avatar answered Sep 30 '22 10:09

Tamas Hegedus


You can also consider adding a custom iterator to your Set or Map:

const mySet = new Set([1, 2, 3, 4, 5]);
const myMap = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
  [4, 'four'],
  [5, 'five']
]);

const customIterator = function () {
  // get the values from the Set or Map to iterate over
  // you could also use .entries() instead of .values()  
  // to get the key/value pairs in case of the Map
  const values = Array.from(this.values());

  // start at the end of the array
  let index = values.length;

  // the custom iterator function returns an object with a next() function
  // this will be called repeatedly by for...of
  return {
    next: function () {
      // the next() function returns an object with a done property
      // to indicate when iteration is completed, and a value property
      // holding the current value
      return {
        done: index === 0,
        // `--` in front, so it decreases 'in place'
        value: values[--index]
      };
    }
  }
};

// add the customIterator to the [Symbol.iterator] property
mySet[Symbol.iterator] = customIterator;
myMap[Symbol.iterator] = customIterator;

// using for...of on the Set
for(const item of mySet) {
  console.log('set:', item);
  // you can also break on some condition e.g.
  // if(item === 3){ break; }
}

// using for...of on the Map
for(const item of myMap) {
  console.log('map:', item);
}

Additional info can be found on MDN:

  • https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols
  • https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set/@@iterator
  • https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map/@@iterator
  • https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator
like image 42
magikMaker Avatar answered Sep 30 '22 11:09

magikMaker