HashSet<T> adalah koleksi tempat setiap item muncul paling banyak satu kali. Koleksi ini tidak punya indeks dan tidak punya urutan yang dijamin, dan sebagai gantinya ia memeriksa keanggotaan dalam waktu yang kurang lebih konstan: mencari satu tag di antara sejuta tag butuh waktu kira-kira sama dengan mencarinya di antara sepuluh tag.
Menambahkan item: Add mengembalikan bool
Output:
True
False
3
True
True
False
2
Menambahkan duplikat bukan sebuah error; Add hanya mengembalikan false dan set-nya tidak berubah. Nilai kembalian itulah hal paling berguna dari method ini. Ia menggabungkan "apakah aku sudah pernah melihat ini?" dan "ingat item ini" dalam satu pemanggilan:
Output:
Duplicate: ana@x.com
Duplicate: ben@x.com
3 unique
Kenapa Contains cepat
List<T>.Contains membandingkan nilai dengan setiap elemen satu per satu, jadi biayanya bertambah seiring panjang list. HashSet<T> menghitung hash code item, langsung menuju bucket untuk kode tersebut, dan hanya membandingkan sedikit item yang tersimpan di sana. Untuk pemeriksaan keanggotaan di dalam loop, langkah O(n) berubah menjadi langkah O(1), dan loop bersarang pada dua list berubah menjadi satu kali jalan:
// Slow on large inputs: Contains scans bannedList for every order.
var flagged = orders.Where(o => bannedList.Contains(o.CustomerId));
// Fast: build the set once, then each lookup is constant time.
var banned = new HashSet<int>(bannedList);
var flagged2 = orders.Where(o => banned.Contains(o.CustomerId));
Membuat set butuh satu kali jalan pada list, jadi cara ini baru menguntungkan kalau kamu melakukan pencarian lebih dari beberapa kali.
Menghapus duplikat dari list
Ada tiga cara yang umum, dan ketiganya berbeda dalam hal apa yang terjadi pada urutan:
Output:
Lima, Oslo, Pune, Kyiv
4
Lima, Oslo, Pune, Kyiv
Distinct adalah pilihan default yang tepat saat kamu ingin hasilnya berupa list. Versi yang langsung mengubah list bekerja karena RemoveAll memanggil predicate sekali untuk setiap elemen secara berurutan: seen.Add mengembalikan false untuk salinan kedua dan seterusnya, jadi tepat salinan-salinan itulah yang dihapus.
Operasi set: union, intersection, selisih
HashSet<T> punya operasi dari teori himpunan. Method ...With mengubah set tempat method itu dipanggil dan tidak mengembalikan apa pun.
Output:
Union: Ana, Ben, Chloe, Dev
Intersect: Ben, Chloe
Except: Ana
Symmetric: Ana, Dev
True
True
False
True
Argumennya bisa berupa IEnumerable<T> apa saja: array, list atau set lain. SetEquals mengabaikan urutan dan duplikat pada argumennya. Fungsi bantu Show mengurutkan sebelum mencetak karena urutan enumerasi sebuah set bukan sesuatu yang bisa diandalkan.
LINQ punya method padanannya yang mengembalikan urutan baru dan tidak mengubah input-nya: monday.Union(tuesday), monday.Intersect(tuesday), monday.Except(tuesday). Pakai method-method itu saat kamu tidak ingin mengubah sebuah set, atau saat input-nya berupa list.
Kesamaan kustom untuk class buatanmu
Sebuah set menentukan "item yang sama" dengan GetHashCode dan Equals. Untuk class yang tidak meng-override keduanya, keduanya didasarkan pada identitas objek, jadi dua objek dengan field yang sama dianggap dua item yang berbeda:
Output:
2
1
True
Aturannya: objek yang Equals harus mengembalikan GetHashCode yang sama. Kalau kamu hanya meng-override Equals, set akan mencari di bucket yang salah dan tetap melaporkan duplikat. Di .NET Core 2.1 ke atas, HashCode.Combine(X, Y) membuat hash code yang baik tanpa perhitungan yang ditulis manual.
Saat kamu tidak bisa mengubah class-nya, atau butuh pengertian "sama" yang berbeda untuk satu set, kirim sebuah IEqualityComparer<T> ke constructor-nya. String sudah dilengkapi comparer siap pakai:
Output:
True
False
2
Di C# 9 ke atas, record membuatkan Equals dan GetHashCode berbasis nilai untukmu, jadi record Point(int X, int Y); bisa dipakai di set tanpa kode tambahan.
Jangan pernah mengubah field yang dipakai oleh GetHashCode saat objeknya ada di dalam set. Objek itu tetap berada di bucket untuk hash lamanya, jadi Contains dan Remove tidak bisa menemukannya lagi.
Urutan dan SortedSet
HashSet<T> melakukan enumerasi dalam urutan yang sebaiknya kamu anggap acak. Kalau kamu butuh item yang terurut, urutkan saat mencetak (set.OrderBy(x => x)) atau pakai SortedSet<T>, yang selalu menjaga item tetap terurut dan menambahkan Min, Max dan query rentang dengan biaya O(log n) per operasi:
var ranks = new SortedSet<int> { 30, 10, 20 };
Console.WriteLine(string.Join(", ", ranks)); // 10, 20, 30
Console.WriteLine(ranks.Min); // 10
HashSet vs List vs Dictionary
| Kebutuhan | Pakai |
|---|---|
| Item unik, cek "apakah ada?" yang cepat | HashSet<T> |
| Item unik, selalu terurut | SortedSet<T> |
| Urutan, duplikat, akses lewat indeks | List<T> |
| Sebuah nilai yang disimpan di bawah setiap key | Dictionary<TKey, TValue> |
Set adalah dictionary yang punya key tapi tidak punya value. Kalau kamu menulis Dictionary<string, bool> hanya untuk mencatat keanggotaan, HashSet<string> menyampaikan hal yang sama dengan lebih jelas.
Kesalahan yang sering terjadi
- Mengharapkan urutan. Set tidak punya urutan yang bisa diandalkan; urutkan, atau pakai
SortedSet<T>. - Class kustom tanpa
EqualsdanGetHashCode. Objek yang terlihat sama menjadi item yang terpisah. - Hanya meng-override
Equals. Selalu overrideGetHashCodebersamanya. - Mengubah item setelah ditambahkan. Set tidak bisa lagi menemukannya.
- Mengakses set lewat indeks.
set[0]tidak bisa di-compile; set tidak punya indeks. Konversi denganToList()kalau kamu butuh posisi.
Pertanyaan yang sering diajukan
Apa itu HashSet di C#?
HashSet<T> adalah koleksi berisi item unik tanpa urutan yang pasti. Menambahkan item yang sudah ada tidak melakukan apa-apa, dan Contains menjawab dalam waktu yang kurang lebih konstan seberapa pun besar set-nya, karena item disimpan berdasarkan hash code seperti key pada Dictionary.
Apa yang dikembalikan HashSet.Add?
Add mengembalikan true saat item berhasil ditambahkan dan false saat item itu sudah ada di set. Karena itu if (!seen.Add(x)) menjadi pemeriksaan duplikat dalam satu baris: item baru ditambahkan dan item yang berulang dilaporkan dalam satu pemanggilan.
Bagaimana cara menghapus duplikat dari List di C#?
list.Distinct().ToList() mengembalikan list baru tanpa duplikat dan mempertahankan kemunculan pertama setiap item sesuai urutan aslinya. new HashSet<T>(list) juga menghapus duplikat, tapi set tidak punya urutan yang dijamin. Untuk menghapus duplikat langsung di list itu sendiri, pakai var seen = new HashSet<T>(); list.RemoveAll(x => !seen.Add(x));.
Kapan sebaiknya memakai HashSet daripada List?
Pakai HashSet<T> saat kamu lebih sering bertanya "apakah item ini ada di koleksi?" atau butuh item yang unik. List<T>.Contains memeriksa setiap elemen, jadi makin lambat saat list-nya makin besar, sedangkan HashSet<T>.Contains tidak. Pakai List<T> saat urutan, duplikat atau akses lewat indeks itu penting.
Kenapa HashSet saya berisi objek yang duplikat?
Class-mu tidak meng-override Equals dan GetHashCode, jadi set membandingkan referensi dan dua objek dengan nilai field yang sama dianggap berbeda. Override kedua method itu (selalu bersamaan), atau kirim sebuah IEqualityComparer<T> ke constructor set.