Reputation: 3530
I have a hashset in C# that I'm removing from if a condition is met while iterating though the hashset and cannot do this using a foreach loop as below.
foreach (String hashVal in hashset)
{
if (hashVal == "somestring")
{
hash.Remove("somestring");
}
}
So, how can I remove elements while iterating?
Upvotes: 34
Views: 37590
Reputation: 31
I do some performance test with the different methods on Unity (So it can works different on other framewoks).
Time with 500k items
int
and ~48ms on Test
class)int
and ~34ms on Test
class)int
and ~33ms on Test
class)int
and ~71ms on Test
class)Test method:
private void Test() {
Debug.Log("Start Test");
HashSet<Test> hashSet = new();
for (int i = 0; i < 500000; i++) {
hashSet.Add(new Test(i));
}
HashSet<Test> testSet = new(hashSet);
long methodOne = 0;
long methodTwo = 0;
long methodThree = 0;
long methodFour = 0;
int repeat = 10;
for (int i = 0; i < repeat; i++) {
testSet = new HashSet<Test>(hashSet);
Stopwatch sw = new();
sw.Start();
foreach (Test test in testSet.ToList()) {
if (test.A % 2 == 0) testSet.Remove(test);
}
sw.Stop();
methodOne += sw.ElapsedMilliseconds;
testSet = new HashSet<Test>(hashSet);
sw.Reset();
sw.Start();
testSet.RemoveWhere(test => test.A % 2 == 0);
sw.Stop();
methodTwo += sw.ElapsedMilliseconds;
testSet = new HashSet<Test>(hashSet);
sw.Reset();
sw.Start();
HashSet<Test> toKeep = new();
foreach (Test test in testSet) {
if (test.A % 2 != 0) toKeep.Add(test);
}
testSet = toKeep;
sw.Stop();
methodThree += sw.ElapsedMilliseconds;
testSet = new HashSet<Test>(hashSet);
sw.Reset();
sw.Start();
HashSet<Test> toRemove = new();
foreach (Test test in testSet) {
if (test.A % 2 == 0) toRemove.Add(test);
}
foreach (Test test in toRemove) {
testSet.Remove(test);
}
sw.Stop();
methodFour += sw.ElapsedMilliseconds;
}
Debug.Log($"Method One: {methodOne / repeat}");
Debug.Log($"Method Two: {methodTwo / repeat}");
Debug.Log($"Method Three: {methodThree / repeat}");
Debug.Log($"Method Four: {methodFour / repeat}");
}
The Test Class:
public class Test {
public int A;
public Test(int a) {
A = a;
}
}
Upvotes: 0
Reputation: 491
there is a much simpler solution here.
var mySet = new HashSet<string>();
foreach(var val in mySet.ToArray() {
Console.WriteLine(val);
mySet.Remove(val);
}
.ToArray() already creates a copy for you. you can loop to your hearts content.
Upvotes: 0
Reputation: 353
For people who are looking for a way to process elements in a HashSet while removing them, I did it the following way
var set = new HashSet<int> {1, 2, 3};
while (set.Count > 0)
{
var element = set.FirstOrDefault();
Process(element);
set.Remove(element);
}
Upvotes: 0
Reputation: 24067
I would avoid using two foreach loop - one foreach loop is enough:
HashSet<string> anotherHashSet = new HashSet<string>();
foreach (var item in hashSet)
{
if (!shouldBeRemoved)
{
anotherSet.Add(item);
}
}
hashSet = anotherHashSet;
Upvotes: 9
Reputation: 83004
Use the RemoveWhere method of HashSet instead:
hashset.RemoveWhere(s => s == "somestring");
You specify a condition/predicate as the parameter to the method. Any item in the hashset that matches the predicate will be removed.
This avoids the problem of modifying the hashset whilst it is being iterated over.
In response to your comment:
's' represents the current item being evaluated from within the hashset.
The above code is equivalent to:
hashset.RemoveWhere(delegate(string s) {return s == "somestring";});
or:
hashset.RemoveWhere(ShouldRemove);
public bool ShouldRemove(string s)
{
return s == "somestring";
}
EDIT:
Something has just occurred to me: since HashSet is a set that contains no duplicate values, just calling hashset.Remove("somestring")
will suffice. There is no need to do it in a loop as there will never be more than a single match.
Upvotes: 59
Reputation: 158379
You can't remove items from a collection while looping over it with an enumerator. Two approaches to solve this are:
HashSet
)Example of the second approach:
HashSet<string> hashSet = new HashSet<string>();
hashSet.Add("one");
hashSet.Add("two");
List<string> itemsToRemove = new List<string>();
foreach (var item in hashSet)
{
if (item == "one")
{
itemsToRemove.Add(item);
}
}
foreach (var item in itemsToRemove)
{
hashSet.Remove(item);
}
Upvotes: 10
Reputation: 28443
Usually when I want to iterate over something and remove values I use:
For (index = last to first)
If(ShouldRemove(index)) Then
Remove(index)
Upvotes: -1