aHunter
aHunter

Reputation: 3530

HashSet Iterating While Removing Items in C#

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

Answers (7)

Zarpyk
Zarpyk

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

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

kewur
kewur

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

Muhammad Charaf
Muhammad Charaf

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

Oleg Vazhnev
Oleg Vazhnev

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

adrianbanks
adrianbanks

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

Fredrik M&#246;rk
Fredrik M&#246;rk

Reputation: 158379

You can't remove items from a collection while looping over it with an enumerator. Two approaches to solve this are:

  • Loop backwards over the collection using a regular indexed for-loop (which I believe is not an option in the case of a HashSet)
  • Loop over the collection, add items to be removed to another collection, then loop over the "to-be-deleted"-collection and remove the items:

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

Nescio
Nescio

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

Related Questions