Start learning free

LESSON 239 OF 347 · HASH TABLE

Hash Sets in C#: Using HashSet<T>

Lesson Read the full explanation

Hash Sets are collections of unique items i.e no element can be repeated.

Hash Sets were designed to give us a way of adding and looking for unique values in a collection in a quick manner.

The values in a Hash Set can be either simple primitives like strings or integers as well as more complex object types like object literals or arrays.

In Java, the Hash Set is implemented as a HashSet object.


Creation:

To create a new empty HashSet that holds Strings, you use the following syntax:

Set<string> mySet = new HashSet<>();

Creating an empty Set takes O(1) time.


Constructing from iterable

The Set constructor also accepts an optional iterable object. If you pass an iterable object to the Set constructor, all the unique elements of the object will be added to the new set:

List<string> elements = new List<>(List.of("b", "c", "c", "d"));
Set<string> mySet = new HashSet<>(elements);

// mySet contains {"b", "c", "d"}

This takes O(n) time, where n is the number of elements in the iterable object.


Adding elements:

To add an element to the set, you use the add() method:

List<string> elements = new List<>(List.of("b", "c", "c", "d"));
Set<string> mySet = new HashSet<>(elements);

// Adding new elements
mySet.Add("x");

// Adding an existing element
mySet.Add("d"); // nothing happens

// mySet contains {"b", "c", "d", "x"}

The Set first checks if that element already exists and if so, it does nothing and also doesn't raise an error.

Adding an element to a Set takes O(1) time.


Checking if a value exists:

To check if a Set has a specific element, you use the contains() method which returns true if the set contains the element, otherwise, it returns false.

List<string> elements = new List<>(List.of("b", "c", "c", "d"));
Set<string> mySet = new HashSet<>(elements);

bool exists = mySet.Contains("c"); // exists is true

exists = mySet.Contains("x"); // exists becomes false

Checking if a value exists in a Set takes O(1) time.


Removing elements:

To delete a specified element from a set, you use the remove() method:

List<string> elements = new List<>(List.of("b", "c", "c", "d"));
Set<string> mySet = new HashSet<>(elements);

// Deleting elements
mySet.Remove("c");

// Deleting a non-existent element
mySet.Remove("z"); // does nothing

// mySet contains {"b", "d"}

The Set first checks if that key exists and if not, it does nothing and also doesn't raise an error, so it's safe to delete non-existing elements.

Removing an element from a Set takes O(1) time.


Iterating over the Set values:

If we want to iterate over values of the Set, we can use the for loop along with the : operator:

List<string> elements = new List<>(List.of("b", "c", "c", "d"));
Set<string> mySet = new HashSet<>(elements);

for (string val : mySet) {
    Console.WriteLine(val);
}

// This will print the following:
// b
// c
// d

Iterating over a Set takes O(n) time.


Space Complexity

A Set uses O(n) space, where n is the number of elements existing in the Set.


Assignment
Follow the Coding Tutorial and let's play with some Hash Sets.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 6
    Ctrl + Enter to check

    C# · HASH TABLE

    Hash Set in C#, explained

    What a hash set is

    A hash set is a collection of unique items. Adding a value that is already present changes nothing, so a set never holds duplicates. It also has no positions: you cannot ask for "the third element", only whether a value is in the set.

    In C#, the type is HashSet<T> from System.Collections.Generic, where T is the element type. It is the right tool for two jobs: removing duplicates, and answering "have I seen this value before?" quickly. Adding, removing and checking a value are all O(1) on average.

    Creating a set

    An empty set of strings:

    HashSet<string> mySet = new HashSet<string>();
    

    The constructor also accepts any collection, such as an array or a list, and copies its unique elements into the set:

    string[] letters = {"b", "c", "c", "d"};
    HashSet<string> mySet = new HashSet<string>(letters);
    // mySet contains "b", "c", "d"
    

    Building from a collection of n elements takes O(n) time. Collection initializers work as well: new HashSet<int> {1, 2, 3}.

    Adding, checking and removing

    The three core operations are Add, Contains and Remove:

    mySet.Add("x");                    // true: "x" was new
    mySet.Add("d");                    // false: "d" was already there, nothing changes
    bool exists = mySet.Contains("c"); // true
    mySet.Remove("c");                 // true: removed
    mySet.Remove("z");                 // false: "z" was not there, nothing happens
    

    All of them return a bool. Add returns whether the value was new, and Remove returns whether something was removed. Neither throws when the answer is "no", so you never need to check first. Because Add reports whether the value was new, if (!seen.Add(x)) is a compact way to detect a repeated value.

    Iterating

    A foreach visits every element once, in O(n) time:

    foreach (string val in mySet) {
        Console.WriteLine(val);
    }
    

    A hash set does not promise any particular order, so do not depend on the output order matching the order you added the values in. The set uses O(n) space for n elements, and mySet.Count gives the current number of elements.

    Set operations

    HashSet<T> also has methods that combine sets in place. UnionWith adds all elements of another collection, and IntersectWith keeps only the elements that appear in both:

    var nums = new HashSet<int> {1, 2, 3};
    nums.UnionWith(new[] {3, 4});      // 1, 2, 3, 4
    nums.IntersectWith(new[] {2, 3, 9}); // 2, 3
    

    Common mistakes

    • Looking for positions. A set has no indexer, so mySet[0] does not compile. Use a List<T> if you need positions.
    • Method names. The C# members are Add, Contains and Remove in PascalCase. Lowercase add or contains, or the type names Set and List.of, are not C#.
    • Strings compare by content, but case matters. "Apple" and "apple" are different elements by default. Pass StringComparer.OrdinalIgnoreCase to the constructor for a case-insensitive set.
    • Checking a list instead. List<T>.Contains scans every element, O(n). For repeated membership checks, a set is much faster.
    • Expecting a stable order, as covered above.

    The exercise

    The lesson builds a set of fruits from an array that contains a duplicate, then uses each operation.

    1. Start with an array. fruitsArray is a string[] holding "apples", "bananas" and "apples".
    2. Build the set. new HashSet<string>(fruitsArray.ToList()) copies the values and drops the repeated "apples". ToList() comes from System.Linq. The constructor would also accept the array directly, because it takes any collection of strings.
    3. Add "oranges". It is new, so the set grows.
    4. Check for apples with Contains and print We have apples if the answer is true.
    5. Remove "bananas".
    6. Print the rest with a foreach over the set.
    string[] fruitsArray = {"apples", "bananas", "apples"};
    
    HashSet<string> fruits = new HashSet<string>(fruitsArray.ToList());
    
    fruits.Add("oranges");
    
    if (fruits.Contains("apples")) {
        Console.WriteLine("We have apples");
    }
    
    fruits.Remove("bananas");
    
    foreach (string fruit in fruits) {
        Console.WriteLine(fruit);
    }
    

    Running it prints:

    We have apples
    apples
    oranges
    

    The set holds only apples and oranges at the end: the duplicate was dropped when the set was built, and the bananas were removed. Since a set has no guaranteed order, a program shouldn't rely on those last two lines coming out in that order.

    Key takeaways

    • HashSet<T> holds unique values with no positions.
    • Add, Contains and Remove are O(1) on average, and each returns a bool instead of throwing.
    • Passing a collection to the constructor removes its duplicates.
    • Do not depend on iteration order.
    • Use a set for membership checks and de-duplication, and a dictionary when each value needs something attached to it.
    Keep going with C#About 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free