Kapan saya harus menggunakan tipe HashSet <T>?

134

Saya sedang menjelajahi HashSet<T> tipenya, tapi saya tidak mengerti di mana koleksi itu berada.

Bisakah seseorang menggunakannya untuk mengganti List<T>? Saya membayangkan kinerja a HashSet<T>menjadi lebih baik, tetapi saya tidak bisa melihat akses individu ke elemen-elemennya.

Apakah hanya untuk pencacahan?

Joan Venge
sumber

Jawaban:

228

Yang penting tentang HashSet<T>ada di sana dalam nama: itu satu set . Satu-satunya hal yang dapat Anda lakukan dengan satu set adalah menetapkan apa anggotanya, dan untuk memeriksa apakah suatu item adalah anggota.

Bertanya apakah Anda dapat mengambil elemen tunggal (mis. set[45]) Adalah salah paham konsep himpunan. Tidak ada yang namanya elemen 45 set. Item dalam satu set tidak memiliki pemesanan. Set {1, 2, 3} dan {2, 3, 1} identik dalam segala hal karena mereka memiliki keanggotaan yang sama, dan keanggotaan adalah yang terpenting.

Ini agak berbahaya untuk diulang lebih dari satu HashSet<T> karena melakukan memaksakan pesanan pada item di set. Urutan itu sebenarnya bukan properti dari himpunan. Anda tidak harus bergantung padanya. Jika pemesanan barang-barang dalam koleksi penting bagi Anda, koleksi itu bukan satu set.

Set sangat terbatas dan dengan anggota yang unik. Di sisi lain, mereka sangat cepat.

Robert Rossney
sumber
1
Fakta bahwa kerangka kerja menyediakan SortedSetstruktur data baik yang bertentangan dengan apa yang Anda katakan tentang pesanan tidak menjadi properti dari set - atau menunjukkan kesalahpahaman dari tim pengembangan.
Veverke
10
Saya pikir itu lebih tepat untuk mengatakan bahwa urutan item dalam HashSettidak didefinisikan, jadi jangan bergantung pada urutan iterator. Jika Anda mengulang set karena Anda melakukan sesuatu terhadap item dalam set, itu tidak berbahaya kecuali jika Anda mengandalkan sesuatu yang berhubungan dengan pesanan. A SortedSetmemiliki semua properti dari perintah HashSet plus , namun SortedSettidak berasal dari HashSet; diulang ulang, SortedSet adalah kumpulan objek berbeda yang diurutkan .
Paket
110

Inilah contoh nyata tempat saya menggunakan HashSet<string>:

Bagian dari stabilo sintaksis saya untuk file UnrealScript adalah fitur baru yang menyoroti komentar gaya Doxygen . Saya harus tahu apakah perintah @atau \valid untuk menentukan apakah akan ditampilkan dalam warna abu-abu (valid) atau merah (tidak valid). Saya memiliki HashSet<string>semua perintah yang valid, jadi setiap kali saya menekan @xxxtoken di lexer, saya menggunakan validCommands.Contains(tokenText)sebagai cek validitas O (1) saya. Saya benar-benar tidak peduli tentang apa pun kecuali keberadaan perintah di set perintah yang valid. Mari kita lihat alternatif yang saya hadapi:

  • Dictionary<string, ?>: Jenis apa yang saya gunakan untuk nilai? Nilai tidak ada artinya karena saya hanya akan menggunakan ContainsKey. Catatan: Sebelum. NET 3.0 ini adalah satu-satunya pilihan untuk pencarian O (1) - HashSet<T>ditambahkan untuk 3.0 dan diperluas untuk diterapkan ISet<T>untuk 4.0.
  • List<string>: Jika saya menjaga daftar diurutkan, saya dapat menggunakan BinarySearch, yaitu O (log n) (tidak melihat fakta ini disebutkan di atas). Namun, karena daftar perintah saya yang valid adalah daftar tetap yang tidak pernah berubah, ini tidak akan pernah lebih tepat daripada sekadar ...
  • string[]: Sekali lagi, Array.BinarySearchmemberikan O (log n) kinerja. Jika daftar ini pendek, ini bisa menjadi opsi dengan kinerja terbaik. Selalu memiliki overhead kurang ruang dari HashSet, Dictionaryatau List. Bahkan dengan BinarySearch, itu tidak lebih cepat untuk set besar, tetapi untuk set kecil itu layak untuk dicoba. Tambang saya memiliki beberapa ratus item, jadi saya meneruskan ini.
Sam Harwell
sumber
24

A HashSet<T>mengimplementasikan ICollection<T>antarmuka:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

Sebuah List<T>implementasi IList<T>, yang memperluasICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

HashSet telah menetapkan semantik, diimplementasikan melalui hashtable secara internal:

Set adalah kumpulan yang tidak mengandung elemen duplikat, dan yang elemennya tidak ada dalam urutan tertentu.

Apa yang didapat HashSet, jika kehilangan perilaku indeks / posisi / daftar?

Menambahkan dan mengambil item dari HashSet selalu oleh objek itu sendiri, bukan melalui pengindeks, dan dekat dengan operasi O (1) (Daftar adalah O (1) tambahkan, O (1) ambil dengan indeks, O (n) cari /menghapus).

Perilaku HashSet dapat dibandingkan dengan menggunakan Dictionary<TKey,TValue>hanya dengan menambahkan / menghapus kunci sebagai nilai, dan mengabaikan nilai kamus itu sendiri. Anda akan berharap kunci dalam kamus tidak memiliki nilai duplikat, dan itulah inti dari bagian "Set".

Kenan EK
sumber
14

Kinerja akan menjadi alasan buruk untuk memilih HashSet daripada Daftar. Sebaliknya, apa yang lebih baik menangkap maksud Anda? Jika pesanan penting, maka Set (atau HashSet) keluar. Jika duplikat diizinkan, juga. Tetapi ada banyak keadaan ketika kita tidak peduli tentang pesanan, dan kami lebih suka tidak memiliki duplikat - dan saat itulah Anda menginginkan Set.

Carl Manaster
sumber
21
Performance would be a bad reason to choose HashSet over List: Saya hanya tidak setuju dengan Anda. Itu semacam mengatakan bahwa memilih Dictionray bukan dua Daftar tidak membantu dalam kinerja. Lihatlah artikel berikut
Oscar Mederos
11
@Oscar: Saya tidak mengatakan bahwa set tidak lebih cepat - saya katakan itu akan menjadi dasar yang buruk untuk memilih mereka. Jika Anda mencoba untuk mewakili koleksi yang dipesan, satu set tidak akan berfungsi dan itu akan menjadi kesalahan untuk mencoba memasukkannya ke dalam; jika koleksi yang Anda inginkan tidak memiliki pesanan, satu set sempurna - dan cepat. Tetapi yang penting adalah pertanyaan pertama: apa yang ingin Anda wakili?
Carl Manaster
2
Tapi pikirkan itu. Jika Anda ingin terus memeriksa apakah string yang diberikan adalah anggota dari beberapa koleksi 10.000 string, secara teknis, string[].Containsdan HashSet<string>.Containsnyatakan maksud Anda dengan baik; alasan untuk memilih HashSet adalah itu akan berjalan lebih cepat.
Casey
12

HashSet adalah a himpunan yang diimplementasikan dengan hashing. Set adalah kumpulan nilai yang tidak mengandung elemen duplikat. Nilai-nilai dalam set juga biasanya tidak terurut. Jadi tidak, satu set tidak dapat digunakan untuk mengganti daftar (kecuali Anda seharusnya menggunakan set di tempat pertama).

Jika Anda bertanya-tanya untuk apa set yang bagus: di mana saja Anda ingin menyingkirkan duplikat, jelas. Sebagai contoh yang sedikit dibuat-buat, katakanlah Anda memiliki daftar 10.000 revisi proyek perangkat lunak, dan Anda ingin mengetahui berapa banyak orang yang berkontribusi pada proyek itu. Anda bisa menggunakan Set<string>dan mengulangi daftar revisi dan menambahkan masing-masing penulis revisi ke set. Setelah Anda selesai iterasi, ukuran set adalah jawaban yang Anda cari.

pangeran
sumber
Tetapi Set tidak mengizinkan pengambilan elemen tunggal? Suka set [45]?
Joan Venge
2
Untuk itu, Anda akan mengulangi set anggota. Operasi khas lainnya sedang memeriksa apakah set berisi elemen atau mendapatkan ukuran set.
earl
11

HashSet akan digunakan untuk menghapus elemen duplikat dalam koleksi IEnumerable. Sebagai contoh,

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

setelah kode-kode itu dijalankan, uniqueStrings memegang {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};

Thomas.Benz
sumber
6

Mungkin penggunaan yang paling umum untuk hashsets adalah untuk melihat apakah mereka mengandung elemen tertentu, yang dekat dengan operasi O (1) untuk mereka (dengan asumsi fungsi hashing yang cukup kuat), sebagai lawan dari daftar yang memeriksa inklusi adalah O ( n) (dan set diurutkan yang merupakan O (log n)). Jadi, jika Anda melakukan banyak pemeriksaan, apakah suatu item terkandung dalam beberapa daftar, hahssets mungkin merupakan peningkatan kinerja. Jika Anda hanya mengulanginya, tidak akan ada banyak perbedaan (iterasi pada seluruh set adalah O (n), sama dengan daftar dan hash memiliki overhead yang agak lebih banyak saat menambahkan item).

Dan tidak, Anda tidak dapat mengindeks satu set, yang tidak masuk akal, karena set tidak dipesan. Jika Anda menambahkan beberapa item, set tidak akan mengingat yang pertama, dan yang kedua dll.

sepp2k
sumber
Jika Anda hanya mengulanginya maka metode HashSet menambahkan sedikit penggunaan memori dibandingkan dengan Daftar.
SamuelWarren
5

HashSet<T>adalah strucutre data dalam kerangka NET. Yang mampu mewakili set matematika sebagai objek. Dalam hal ini, ia menggunakan kode hash ( GetHashCodehasil dari setiap item) untuk membandingkan kesetaraan elemen yang ditetapkan.

Himpunan berbeda dari daftar karena hanya memungkinkan satu kemunculan elemen yang sama yang terkandung di dalamnya. HashSet<T>hanya akan kembali falsejika Anda mencoba menambahkan elemen identik kedua. Memang, pencarian elemen sangat cepat ( O(1)waktu), karena struktur data internal hanyalah sebuah hashtable.

Jika Anda bertanya-tanya mana yang harus digunakan, perhatikan bahwa menggunakan di List<T>mana HashSet<T>yang tepat bukanlah kesalahan terbesar, meskipun mungkin berpotensi menimbulkan masalah di mana Anda memiliki item duplikat yang tidak diinginkan dalam koleksi Anda. Terlebih lagi, pencarian (pengambilan item) jauh lebih efisien - idealnya O(1)(untuk bucket sempurna) daripada O(n)waktu - yang cukup penting dalam banyak skenario.

Noldorin
sumber
1
Menambahkan item yang ada ke set tidak akan menghasilkan pengecualian. Tambah hanya akan mengembalikan false. Juga: pencarian hash secara teknis adalah O (n), bukan O (1), kecuali jika Anda memiliki fungsi hashing yang sempurna. Tentu saja dalam praktiknya Anda akan lolos dengan asumsi O (1) kecuali fungsi hashing benar-benar buruk.
sepp2k
1
@ sepp2k: Ya, jadi mengembalikan boolean ... Intinya, ia memberi tahu Anda. Dan hash look up adalah kasus terburuk O (n) jika Anda menambal mengerikan - itu jauh lebih dekat dengan O (1) secara umum.
Noldorin
4

List<T>digunakan untuk menyimpan set informasi yang dipesan. Jika Anda mengetahui urutan relatif dari elemen daftar, Anda dapat mengaksesnya dalam waktu yang konstan. Namun, untuk menentukan di mana elemen terletak di daftar atau untuk memeriksa apakah ada dalam daftar, waktu pencarian adalah linier. Di samping itu,HashedSet<T> tidak membuat jaminan urutan data yang disimpan dan akibatnya memberikan waktu akses yang konstan untuk elemen-elemennya.

Seperti namanya, HashedSet<T>adalah struktur data yang mengimplementasikan set semantik . Struktur data dioptimalkan untuk mengimplementasikan operasi yang ditetapkan (yaitu Union, Difference, Intersect), yang tidak dapat dilakukan seefisien dengan implementasi Daftar tradisional.

Jadi, untuk memilih tipe data mana yang akan digunakan sangat tergantung pada apa yang Anda coba lakukan dengan aplikasi Anda. Jika Anda tidak peduli tentang bagaimana elemen Anda disusun dalam koleksi, dan hanya ingin merinci atau memeriksa keberadaannya, gunakan HashSet<T>. Kalau tidak, pertimbangkan untuk menggunakan List<T>atau struktur data lain yang sesuai.

Steve Guidi
sumber
2
Peringatan lain: set umumnya memungkinkan hanya satu kemunculan elemen.
Steve Guidi
1

Singkatnya - kapan saja Anda tergoda untuk menggunakan Kamus (atau Kamus di mana S adalah properti T) maka Anda harus mempertimbangkan HashSet (atau HashSet + menerapkan IEquatable pada T yang sama dengan S)

Addys
sumber
5
Kecuali Anda peduli dengan kunci, maka Anda harus menggunakan kamus.
Hardwareguy
1

Dalam skenario yang dimaksudkan dasar HashSet<T>harus digunakan ketika Anda ingin operasi set yang lebih spesifik pada dua koleksi daripada menyediakan LINQ. Metode LINQ seperti Distinct, Union, Intersectdan Exceptcukup dalam kebanyakan situasi, tapi kadang-kadang Anda mungkin perlu operasi lebih berbutir halus, dan HashSet<T>menyediakan:

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

Perbedaan lain antara HashSet<T>metode LINQ dan "tumpang tindih" adalah bahwa LINQ selalu mengembalikan yang baru IEnumerable<T>, dan HashSet<T>metode memodifikasi koleksi sumber.

c_buk
sumber