Skip to content Skip to sidebar Skip to footer

Is It Safe To Delete Elements In A Set While Iterating With For..of?

Is it specified that you can delete any element in an instance of Set while iterating using for..of and that you won't iterate more than once on an element you won't miss any othe

Solution 1:

Yes, it is perfectly fine to add elements and remove elements to a set while iterating it. This use case was considered and is supported in JavaScript 2015 (ES6). It will leave it in a consistent state. Note this also applies to itearting with forEach.

Intuitively:

The set iteration algorithm basically looks something like this:

Set position to 0
While position < calculateLength() // note it's calculated on each iteration
    return the element at set.entryList[position]

Addition just looks something like this:

If element notinsetAdd element to the _end_ of the set

So it does not interfere with existing iterations - they will iterate it.

Deletion looks something like this:

Replace all elements withare equal to `element` with a special emptyvalue

Replacing it with an empty value rather than deleting it ensures it will not mess up with iterators' positions.


Formally

Addition

Here is the relevant part of the specification from %SetIteratorPrototype%.next:

Repeat while index is less than the total number of elements of entries. The number of elements must be redetermined each time this method is evaluated.

The set iterator proceeds to iterate the entries one by one.

From Set.prototype.add:

Append value as the last element of entries.

This ensures that when adding elements to the list it will be iterated before the iteration completes since it always gets a new slot in the entries list. Thus this will work as the spec mandates.

As for deletion:

Replace the element of entries whose value is e with an element whose value is empty.

Replacing it with an empty element rather than removing it ensures that the iteration order of existing iterators will not get out or order and they will continue iterating the set correctly.

With code

Here is a short code snippet that demonstrates this ability

varset = new Set([1]);
for(let item of set){
   if(item < 10) set.add(item+1);
   console.log(item);
}

Which logs the numbers 1 to 10. Here is a version not using for... of you can run in your browser today:

var set = newSet([1]);
for (var _i = set[Symbol.iterator](), next; !(next = _i.next()).done;) {
   var item = next.value;
   if (item < 10) set.add(item + 1);
   document.body.innerHTML += " " + item;
}

Solution 2:

My answer is yes, if you're okay with it continuing onto the next value in the Set in the next iteration after the deletion. It doesn't even seem to matter which instance of the Set you're currently on in the iteration process. Pretty ideal!


Here's my test code:

s = newSet([ { a: 0 }, { a: 1 }, { a: 2 }, { a: 3 } ]);
do {
  for (let x of s) {
    console.log(x.a);
    if (Math.random() < 0.2) {
      console.log('deleted ' + x.a);
      s.delete(x);
    }
  }
} while (s.size > 0);

In Firefox 75.0, it works just fine. Sets are supposed to maintain their insertion order, and it does, it prints it out in that order as it iterates through. Regardless of what gets deleted, it continues in the insertion order:

0
1
2
3
0
1
deleted 1
2
3
0
2
deleted 2
3
0
3
0
deleted 0
3
3
...
3
3
deleted 3

I also tested with similar code but that doesn't use the current instance of the iteration process:

sCopy = [{ a: 0 }, { a: 1 }, { a: 2 }, { a: 3 }];
s = newSet(sCopy);
do {
  for (let x of s) {
    console.log(x.a);
    if (Math.random() < 0.2) {
      let deleteMe = Math.floor(Math.random() * s.size);
      console.log('deleted ' + sCopy[deleteMe].a);
      s.delete(sCopy[deleteMe]);
      sCopy.splice(deleteMe, 1);
    }
  }
} while (s.size > 0);

I had to use an adjacent array because there's no way to look up a random index of a Set, to delete a random instance. So I just created the Set from the array so it uses the same object instances.

That works great as well, as you can see:

0
deleted 1
2
deleted 2
3
0
3
0
deleted 0
3
3
3
3
deleted 3

And yes... I even tested with random object instance insertion as well... Same deal, I won't post the output this time:

sCopy = [{ a: 0 }, { a: 1 }, { a: 2 } ];
s = newSet(sCopy);
do {
  for (let x of s) {
    console.log(x.a);
    if (Math.random() < 0.1) {
      let newInstance = { a: Math.random() * 100 + 100 };
      console.log('added ' + newInstance.a);
      s.add(newInstance);
      sCopy.push(newInstance);
    }
    if (Math.random() < 0.2) {
      let deleteMe = Math.floor(Math.random() * s.size);
      console.log('deleted ' + sCopy[deleteMe].a);
      s.delete(sCopy[deleteMe]);
      sCopy.splice(deleteMe, 1);
    }
  }
} while (s.size > 0);

Post a Comment for "Is It Safe To Delete Elements In A Set While Iterating With For..of?"