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

4

28

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?

Minetta answered 13/1, 2017 at 12:47 Comment(0)
F
23

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);
}
Foulk answered 13/1, 2017 at 12:57 Comment(2)
Is there not a possibility to to index each item from - 1 and to make index- - until the index of the item is also 0?Linlithgow
@Linlithgow You cannot index into a set. If you have an array you can do that, but then it is shorter (in code) to reverse it.Foulk
C
6

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:

Cauley answered 19/7, 2019 at 18:44 Comment(0)
A
2

var mySet = new Set([1,2,3,4,5]);
var reverse = [...mySet].reverse();
for(let myNum of reverse){
 console.log(myNum);
}
Almandine answered 13/1, 2017 at 12:55 Comment(0)
V
0

You could also use reduceRight() for the loop and ignore the returned callback value.

  • Depending on your loop task, reduceRight() can be up to 20% faster than reverse()..
  • For larger datasets (>1000 items) reverse() + for(){} loop seems to be faster..

Bench: https://jsben.ch/8vWhD

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

console.log('⎯⎯⎯⎯⎯ Set ⎯⎯⎯⎯⎯');
Array.from(mySet).reduceRight((_, v, i) => console.log(`i = ${i}; v = ${v}`), null);
console.log('⎯⎯⎯⎯⎯ Map ⎯⎯⎯⎯⎯');
Array.from(myMap).reduceRight((_, [k, v]) => console.log(`k = ${k}; v = ${v}`), null);
Vain answered 20/5, 2021 at 19:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.