Start learning free

LESSON 237 OF 674 · SETS

Removing From Sets in C#

Lesson Read the full explanation

TL ; DR:

Remove(x) takes x out of a set. A foreach loop visits every value once, in no fixed order.


HashSet<string> colors = new HashSet<string>(new[] {"red", "blue", "green"});

colors.Remove("blue");

colors.Remove("pink");          // not in it, nothing happens

foreach (string color in colors) {
    Console.WriteLine(color);   // red and green, in any order
}


Full lesson:

s.Remove(x) removes x from the set s. It returns true if x was there. If it wasn't, nothing happens and it returns false.

foreach (string x in s) visits each value of the set once. The order isn't fixed. It may differ from the order you added the values in.

So loop over a set when the order doesn't matter. A total, a count and a check are fine.

Be careful when you change a set inside a loop over it. Removing a value there works in C#. Adding one stops the program with an InvalidOperationException. The safe habit: remove first, then loop.


Your task

In a word game, the set found holds the words a player found. The words "qqq" and "zzz" aren't allowed. Remove both from found.

The player never found "zzz", so removing it changes nothing.

Then add up the points. A word scores one point per letter. Loop over found and add each word's length to score.

The code already prints how many words are left, and the points.

Expected output:

4 words
14 points
STEP 1 OF 3
    Ctrl + Enter to check

    C# · SETS

    Removing From Sets in C#, explained

    Remove and foreach

    A HashSet has two more everyday operations: taking a value out, and visiting every value.

    HashSet<string> colors = new HashSet<string>(new[] {"red", "blue", "green"});
    colors.Remove("blue");
    colors.Remove("pink");          // not in it, nothing happens
    foreach (string color in colors) {
        Console.WriteLine(color);   // red and green, in any order
    }
    

    s.Remove(x) takes x out of the set s. It returns true if x was there. If it wasn't, nothing happens: no error, and the return value is false. This makes Remove safe to call without checking Contains first.

    foreach (string x in s) visits each value once. The order isn't fixed, and it may differ from the order you added the values in. For the example above, the loop prints red and green.

    Using the answer of Remove

    Because Remove reports whether it removed something, you can use it as the test itself:

    HashSet<string> left = new HashSet<string>(new[] {"Ava", "Leo"});
    if (left.Remove("Leo")) {
        Console.WriteLine("Leo removed");
    }
    if (!left.Remove("Zed")) {
        Console.WriteLine("Zed was not there");
    }
    

    Both messages print, one after the other. The set ends up holding only Ava.

    When looping a set makes sense

    Loop over a set when the order doesn't matter. A total, a count and a check are fine, because they come out the same in any order. If you need the values in a particular order, put them in a list and sort it first.

    Don't change a set while looping over it

    Adding to a set inside a foreach over it throws an InvalidOperationException (the message is "Collection was modified; enumeration operation may not execute."). Lists behave the same way for both Add and Remove. On current .NET, HashSet.Remove inside a loop does not throw, but mixing the two jobs is still a habit worth avoiding: the loop and the changes are easier to follow when you remove first and loop afterwards. If you must remove the values that match a rule, RemoveWhere does it in one call:

    HashSet<int> nums = new HashSet<int>(new[] {1, 2, 3, 4, 5, 6});
    nums.RemoveWhere(n => n % 2 == 0);   // removes 2, 4 and 6
    

    That leaves 1, 3 and 5.

    The exercise

    In a word game, the set found holds the words a player found: cat, tree, sun, qqq and moon. The words "qqq" and "zzz" aren't allowed, so remove both. The player never found "zzz", so removing it changes nothing. Then add up the points: a word scores one point per letter.

    1. Call found.Remove("qqq") and found.Remove("zzz"), one per line.
    2. Below int score = 0;, write a foreach loop over found with a string word.
    3. Inside the loop, add word.Length to score.
    class Solution {
        public void MainFunction() {
            List<string> words = new List<string>(new[] {"cat", "tree", "sun", "qqq", "moon"});
            HashSet<string> found = new HashSet<string>(words);
            found.Remove("qqq");
            found.Remove("zzz");
            Console.WriteLine(found.Count + " words");
            int score = 0;
            foreach (string word in found) {
                score += word.Length;
            }
            Console.WriteLine(score + " points");
        }
    }
    

    Output:

    4 words
    14 points
    

    Four words are left: cat (3), tree (4), sun (3) and moon (4), which add up to 14 in any visiting order.

    Common mistakes in the exercise

    • Guarding Remove("zzz") with an error check. No check is needed. The word isn't in the set, and Remove just returns false.
    • Forgetting the removal before the first print. The line that prints the number of words comes after the removals, so it shows 4 and not 5.
    • Adding 1 per word instead of its length. A word scores one point per letter, so the loop adds word.Length. Adding 1 would give 4 points, not 14.
    • Expecting a printed order. This exercise prints only totals, so the order the loop visits the words in never shows.

    Key takeaways

    • Remove(x) returns true or false and never fails for a missing value.
    • foreach visits each value once, in no fixed order.
    • Don't add to a set while looping over it; remove first, then loop.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free