Introduction

Welcome to our informative session, in which we will explore the inner workings of C#'s HashSet structure. Our aim is to gain a comprehensive understanding of how HashSet operates, learn how to apply these structures practically, and get detailed insights into their time and space complexities.

In the programming world, we frequently use a Set when managing a collection of unique items. HashSet in C# is a specific implementation within the System.Collections.Generic namespace, providing benefits such as efficient membership checks and automatic duplicate removal. Today, we will delve into this distinct structure and its practical applications. Ready? Let's embark on this learning journey!

Understanding HashSets

A HashSet is a significant part of C#'s collections framework designed to store unique elements in an unordered way. Unlike arrays or lists, HashSet does not concern itself with the order of elements added. This flexibility ensures that every stored element is unique, giving developers a powerful tool for managing collections of non-repeating data.

A HashSet shines in implementations where the unique constraint is critical, optimizing scenarios that involve checking for existing items or storing distinct data. Let's consider this using a simple C# code snippet:

using System;
using System.Collections.Generic;

class Solution
{
    static void Main(string[] args)
    {
        // Instantiate a HashSet
        HashSet<string> names = new HashSet<string>();

        // Add elements to HashSet
        names.Add("David");
        names.Add("Alice");
        names.Add("Bob");
        names.Add("Alice");

        Console.WriteLine(string.Join(", ", names));  // prints Bob, Alice, David (order may vary)
        Console.WriteLine(names.Count);  // prints 3
    }
}

In this example, despite adding "Alice" twice to our HashSet, it includes "Alice" only once when printed to the console. Notice that HashSet doesn't maintain the order of elements, so "Bob" might appear before "David" or "Alice," illustrating its unordered nature.

HashSet Implementation

Under the hood, HashSet uses a hash table to organize its elements. It employs an array and a hash function that generates a hash code, which simplifies both the storage and retrieval operations. The hash function converts elements like "David" or "Alice" into integers—hash codes (e.g., 100 for "David" and 45 for "Alice"). These hash codes are then used to determine the bucket index where each element is stored in the hash table, allowing for efficient storage and retrieval.

In C#, the operations Add(), Remove(), and Contains() on a HashSet rely on the hash code of the objects. When adding or accessing an object, the GetHashCode method computes a hash, directing to the specific bucket where the object will be stored or located.

Here's an example demonstrating the efficiency of HashSet in handling collisions:

using System;
using System.Collections.Generic;

class Solution
{
    static void Main(string[] args)
    {
        HashSet<int> numbers = new HashSet<int>();

        // Add elements to HashSet
        for (int i = 0; i < 100; i++)
        {
            numbers.Add(i);
        }

        // Access all elements
        for (int i = 0; i < 100; i++)
        {
            if (numbers.Contains(i))
            {
                Console.WriteLine($"{i} found");
            }
        }
    }
}

In this snippet, we add numbers from 0 to 99 to the HashSet and then verify if each number is present. The GetHashCode method ensures swift lookups, enhancing our code execution performance.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal