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 aList<T>if you need positions. - Method names. The C# members are
Add,ContainsandRemovein PascalCase. Lowercaseaddorcontains, or the type namesSetandList.of, are not C#. - Strings compare by content, but case matters.
"Apple"and"apple"are different elements by default. PassStringComparer.OrdinalIgnoreCaseto the constructor for a case-insensitive set. - Checking a list instead.
List<T>.Containsscans 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.
- Start with an array.
fruitsArrayis astring[]holding"apples","bananas"and"apples". - Build the set.
new HashSet<string>(fruitsArray.ToList())copies the values and drops the repeated"apples".ToList()comes fromSystem.Linq. The constructor would also accept the array directly, because it takes any collection of strings. - Add
"oranges". It is new, so the set grows. - Check for apples with
Containsand printWe have applesif the answer is true. - Remove
"bananas". - Print the rest with a
foreachover 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,ContainsandRemoveareO(1)on average, and each returns aboolinstead 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.