I want to be able to remove multiple elements from a set while I am iterating over it. Initially I hoped that iterators were smart enough for the naive solution below to wor
Why don't you use the iterator's remove method on the objects you want to remove?
Iterators were introduced mainly because enumerators couldn't handle deleting while enumerating.
There's a simple answer to this - use the Iterator.remove() method.
Instead of iterating through all the elements in the Set to remove the ones you want, you can actually use Google Collections (not something you can't do it on your own though) and apply a Predicate to mask the ones you don't need.
package com.stackoverflow.q1675037;
import java.util.HashSet;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
public class SetTest
{
public void testFilter(final Set<String> original, final Set<String> toRemove, final Set<String> expected)
{
Iterable<String> mask = Iterables.filter(original, new Predicate<String>()
{
@Override
public boolean apply(String next) {
return !toRemove.contains(next);
}
});
HashSet<String> filtered = Sets.newHashSet(mask);
Assert.assertEquals(original.size() - toRemove.size(), filtered.size());
Assert.assertEquals(expected, filtered);
}
@Test
public void testFilterNone()
{
Set<String> original = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
Set<String> toRemove = new HashSet();
Set<String> expected = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
this.testFilter(original, toRemove, expected);
}
@Test
public void testFilterAll()
{
Set<String> original = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
Set<String> toRemove = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
HashSet<String> expected = new HashSet<String>();
this.testFilter(original, toRemove, expected);
}
@Test
public void testFilterOne()
{
Set<String> original = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
Set<String> toRemove = new HashSet<String>(){
{
this.add("foo");
}
};
Set<String> expected = new HashSet<String>(){
{
this.add("bar");
this.add("foobar");
}
};
this.testFilter(original, toRemove, expected);
}
@Test
public void testFilterSome()
{
Set<String> original = new HashSet<String>(){
{
this.add("foo");
this.add("bar");
this.add("foobar");
}
};
Set<String> toRemove = new HashSet<String>(){
{
this.add("bar");
this.add("foobar");
}
};
Set<String> expected = new HashSet<String>(){
{
this.add("foo");
}
};
this.testFilter(original, toRemove, expected);
}
}
If you have enough memory for one copy of the set, I'll assume you also have enough memory for two copies. The Kafka-esque rules you cite don't seem to forbid that :)
My suggestion, then:
fillSet(set);
fillSet(copy);
for (Object item : copy) {
if (set.contains(item)) { // ignore if not
set.removeAll(setOfStuffToRemove())
}
}
so copy stays intact and just provides the stuff to loop on, while set suffers deletions. Stuff that was removed from set in the meantime will be ignored.