HashSet
HashSet är den unika samlingen - som en exklusiv klubb där samma person inte kan komma in två gånger! 🌟
🎯 Efter denna artikel kommer du att:
- Förstå unikhet - bara ett av varje element, automatisk deduplicering ✨
- Använda Set-operationer - Union, Intersection, Except för matematiska operationer 🧮
- Optimera prestanda - O(1) Contains istället för O(n) som List 🚀
- Veta när HashSet är rätt val - och när det INTE är det! 💡
🌟 **HashSet - Den unika samlingen**
Grundläggande koncept
HashSet = “unik samling” - tänk som en exklusiv klubb eller ett team utan dubbletter.
- Bara unika element - dubbletter ignoreras automatiskt
- Ingen ordning - element är inte sorterade
- Supersnabb sökning - O(1) prestanda för Contains, Add, Remove
- Set-operationer - Union, Intersection, Except för matematiska operationer
using System;
using System.Collections.Generic;
HashSet<string> uniqueColors = new HashSet<string>();
// Add - lägg till element (returnerar bool)
bool added1 = uniqueColors.Add("Röd"); // true - lades till
bool added2 = uniqueColors.Add("Blå"); // true - lades till
bool added3 = uniqueColors.Add("Röd"); // false - redan finns!
Console.WriteLine($"Antal färger: {uniqueColors.Count}"); // 2 (inte 3!)
// Automatisk deduplicering
string[] colors = {"Röd", "Blå", "Grön", "Röd", "Blå", "Gul"};
HashSet<string> uniqueFromArray = new HashSet<string>(colors);
Console.WriteLine($"Unika färger: {uniqueFromArray.Count}"); // 4 (Röd, Blå, Grön, Gul)
**HashSet Methods och Properties**
HashSet<int> numbers = new HashSet<int>{1, 2, 3, 4, 5};
// Basic Properties
Console.WriteLine($"Count: {numbers.Count}"); // 5
// Add - lägg till element (returnerar bool)
bool wasAdded = numbers.Add(6); // true
bool alreadyExists = numbers.Add(3); // false
// Contains - kontrollera om element finns (O(1)!)
bool contains3 = numbers.Contains(3); // true
bool contains10 = numbers.Contains(10); // false
// Remove - ta bort element (returnerar bool)
bool wasRemoved = numbers.Remove(5); // true
bool notFound = numbers.Remove(99); // false
// Clear - ta bort alla element
// numbers.Clear();
// CopyTo - kopiera till array
int[] array = new int[numbers.Count];
numbers.CopyTo(array);
// SetEquals - kontrollera om samma element (ordning spelar ingen roll)
HashSet<int> other = new HashSet<int>{4, 3, 2, 1, 6}; // Olika ordning
bool areEqual = numbers.SetEquals(other); // true - samma element
// IsSubsetOf / IsProperSubsetOf
HashSet<int> subset = new HashSet<int>{1, 2, 3};
bool isSubset = subset.IsSubsetOf(numbers); // true
bool isProperSubset = subset.IsProperSubsetOf(numbers); // true
// IsSupersetOf / IsProperSupersetOf
bool isSuperset = numbers.IsSupersetOf(subset); // true
// Overlaps - kontrollera om några gemensamma element
HashSet<int> other2 = new HashSet<int>{3, 7, 8};
bool hasCommon = numbers.Overlaps(other2); // true (3 är gemensam)
Set-operationer - Matematiska operationer
HashSet<string> avengers = new HashSet<string>
{
"Iron Man", "Thor", "Hulk", "Captain America", "Black Widow"
};
HashSet<string> xmen = new HashSet<string>
{
"Wolverine", "Storm", "Cyclops", "Jean Grey", "Iron Man" // Iron Man är i båda!
};
HashSet<string> dcHeroes = new HashSet<string>
{
"Superman", "Batman", "Wonder Woman", "Flash"
};
// UnionWith - lägg till alla element från annan collection (modifierar original)
HashSet<string> allHeroes = new HashSet<string>(avengers);
allHeroes.UnionWith(xmen);
allHeroes.UnionWith(dcHeroes);
Console.WriteLine($"Alla hjältar: {allHeroes.Count}"); // 12 - inga dubbletter
// IntersectWith - behåll bara gemensamma element (modifierar original)
HashSet<string> commonHeroes = new HashSet<string>(avengers);
commonHeroes.IntersectWith(xmen);
Console.WriteLine($"Gemensamma: {string.Join(", ", commonHeroes)}"); // "Iron Man"
// ExceptWith - ta bort element som finns i annan collection
HashSet<string> onlyAvengers = new HashSet<string>(avengers);
onlyAvengers.ExceptWith(xmen);
Console.WriteLine($"Bara Avengers: {onlyAvengers.Count}"); // 4 - utan Iron Man
// SymmetricExceptWith - behåll element som bara finns i en av grupperna
HashSet<string> uniqueToEach = new HashSet<string>(avengers);
uniqueToEach.SymmetricExceptWith(xmen);
// Resultat: alla utom Iron Man (som finns i båda)
Praktiska användningsområden
// 1. Ta bort dubbletter från List
List<string> duplicateNames = new List<string>
{
"Anna", "Bert", "Anna", "Cilla", "Bert", "David", "Anna"
};
HashSet<string> uniqueNames = new HashSet<string>(duplicateNames);
List<string> cleanedList = new List<string>(uniqueNames);
Console.WriteLine($"Från {duplicateNames.Count} till {cleanedList.Count} unika namn");
// 2. Snabb medlemskapstest
HashSet<string> validUsernames = new HashSet<string>
{
"admin", "user", "guest", "moderator", "editor"
};
bool IsValidUser(string username)
{
return validUsernames.Contains(username); // O(1) - supersnabbt!
}
// Jämför med List (O(n) - långsamt för stora listor)
List<string> validUsernamesList = new List<string>(validUsernames);
bool SlowValidation(string username)
{
return validUsernamesList.Contains(username); // O(n) - blir långsammare
}
// 3. Tag system för blogginlägg
HashSet<string> postTags = new HashSet<string>();
postTags.Add("C#");
postTags.Add("Programming");
postTags.Add("Tutorial");
postTags.Add("C#"); // Ignoreras - redan finns
// 4. Besökarspårning
HashSet<string> todaysVisitors = new HashSet<string>();
void TrackVisitor(string userId)
{
if (todaysVisitors.Add(userId))
{
Console.WriteLine($"Välkommen {userId}!");
}
else
{
Console.WriteLine($"Välkommen tillbaka {userId}!");
}
}
// 5. Algoritmer - besökta noder i graf/träd
HashSet<int> visitedNodes = new HashSet<int>();
void DepthFirstSearch(GraphNode node)
{
if (visitedNodes.Contains(node.Id))
return; // Redan besökt
visitedNodes.Add(node.Id);
Console.WriteLine($"Besöker nod: {node.Id}");
foreach (var neighbor in node.Neighbors)
{
DepthFirstSearch(neighbor);
}
}
🆚 HashSet vs andra Collections
**HashSet vs List**
// Performance test - 10,000 element
List<int> numbersList = new List<int>();
HashSet<int> numbersSet = new HashSet<int>();
// Fyll båda med samma data
for (int i = 0; i < 10000; i++)
{
numbersList.Add(i);
numbersSet.Add(i);
}
// Contains test
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// List.Contains - O(n) - blir långsammare med fler element
bool foundInList = numbersList.Contains(9999); // ~0.1ms för 10k element
stopwatch.Restart();
// HashSet.Contains - O(1) - samma tid oavsett antal element
bool foundInSet = numbersSet.Contains(9999); // ~0.001ms
Console.WriteLine($"List: {foundInList}, HashSet: {foundInSet}");
| Feature | HashSet | List | Array |
|---|---|---|---|
| Dubbletter | ❌ Nej | ✅ Ja | ✅ Ja |
| Ordning | ❌ Ingen | ✅ Bevaras | ✅ Bevaras |
| Indexering | ❌ Nej | ✅ Ja | ✅ Ja |
| Contains | ⚡ O(1) | 🐌 O(n) | 🐌 O(n) |
| Add/Remove | ⚡ O(1) | ⚡ O(1)* | ❌ N/A |
| Memory | 📈 Mer | 📊 Medium | 📉 Minst |
*List.Add är O(1) amortized, men Insert/Remove kan vara O(n)
När använda HashSet vs andra
// ✅ Använd HashSet när:
// - Du behöver bara unika element
HashSet<string> uniqueEmails = new HashSet<string>();
// - Snabb Contains är viktig
HashSet<int> allowedIds = new HashSet<int>{1, 5, 10, 15, 20};
if (allowedIds.Contains(userId)) { /* snabbt! */ }
// - Set-operationer behövs
HashSet<string> permissions1 = new HashSet<string>{"read", "write"};
HashSet<string> permissions2 = new HashSet<string>{"write", "delete"};
permissions1.IntersectWith(permissions2); // gemensamma: {"write"}
// ❌ Använd INTE HashSet när:
// - Du behöver ordning
// List<string> prioritizedTasks = new List<string>(); // ordning viktig
// - Du behöver indexering
// List<int> scores = new List<int>();
// int thirdScore = scores[2]; // kan inte göra med HashSet
// - Du tillåter dubbletter
// List<string> shoppingCart = new List<string>(); // 2 mjölk är OK
⚡ Performance och Best Practices
HashSet Capacity och Performance
// ✅ Sätt initial kapacitet om du vet storleken
HashSet<int> efficientSet = new HashSet<int>(10000); // Undviker rehashing
// ✅ Använd korrekt equality comparer för custom objects
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
return obj is Person p && Name == p.Name && Age == p.Age;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age); // .NET Core 2.1+
// return Name.GetHashCode() ^ Age.GetHashCode(); // Äldre .NET
}
}
HashSet<Person> people = new HashSet<Person>();
people.Add(new Person { Name = "Anna", Age = 25 });
people.Add(new Person { Name = "Anna", Age = 25 }); // Samma - läggs inte till
// ✅ Använd StringComparer för case-insensitive strings
HashSet<string> caseInsensitive = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
caseInsensitive.Add("hello");
caseInsensitive.Add("HELLO"); // Ignoreras - samma som "hello"
Memory och Thread Safety
// HashSet är INTE thread-safe
HashSet<int> unsafeSet = new HashSet<int>();
// ✅ För thread-safety, använd locks
private readonly object lockObject = new object();
void ThreadSafeAdd(int value)
{
lock (lockObject)
{
unsafeSet.Add(value);
}
}
// ✅ Eller använd ConcurrentDictionary som Set
var concurrentSet = new System.Collections.Concurrent.ConcurrentDictionary<int, byte>();
bool ConcurrentAdd(int value)
{
return concurrentSet.TryAdd(value, 0); // Value är irrelevant
}
bool ConcurrentContains(int value)
{
return concurrentSet.ContainsKey(value);
}
🎯 Advanced HashSet Patterns
// 1. Caching med expiration
HashSet<string> cachedResults = new HashSet<string>();
Dictionary<string, DateTime> cacheExpiry = new Dictionary<string, DateTime>();
void AddToCache(string item, TimeSpan expiry)
{
cachedResults.Add(item);
cacheExpiry[item] = DateTime.Now.Add(expiry);
}
bool IsInValidCache(string item)
{
if (!cachedResults.Contains(item))
return false;
if (DateTime.Now > cacheExpiry[item])
{
cachedResults.Remove(item);
cacheExpiry.Remove(item);
return false;
}
return true;
}
// 2. Bloom Filter simulation (falsk-positiv möjlig, aldrig falsk-negativ)
HashSet<int> bloomFilter = new HashSet<int>();
void BloomAdd(string item)
{
bloomFilter.Add(item.GetHashCode());
}
bool MightContain(string item)
{
return bloomFilter.Contains(item.GetHashCode()); // Kanske finns
}
// 3. Set partitioning
HashSet<int> allNumbers = new HashSet<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
HashSet<int> evens = new HashSet<int>();
HashSet<int> odds = new HashSet<int>();
foreach (int num in allNumbers)
{
if (num % 2 == 0)
evens.Add(num);
else
odds.Add(num);
}
// Verify partition
bool isCompletePartition = evens.Union(odds).SetEquals(allNumbers) &&
!evens.Overlaps(odds);
🎓 TL;DR
- **HashSet
**: Unika element, ingen ordning, O(1) Contains/Add/Remove - Användning: Deduplicering, snabb membership test, set-operationer
- Set-operationer: Union, Intersection, Except, SymmetricExcept
- Performance: Mycket snabbare Contains än List för stora datamängder
- Begränsningar: Ingen ordning, ingen indexering, kräver bra GetHashCode()
Obligatorisk dad-joke
Varför är HashSet som en exklusiv nattklubb?
För att de bara släpper in unika personer, och när du väl är inne kan de hitta dig superschnabbt - men du vet aldrig vilken ordning du kommer ut i! 🌟🎭