HashSet <T> türünü ne zaman kullanmalıyım?


135

HashSet<T>Türü keşfediyorum ama koleksiyonlarda nerede durduğunu anlamıyorum.

Biri onu değiştirmek için kullanabilir List<T>mi? A'nın performansının HashSet<T>daha iyi olacağını hayal ediyorum , ancak öğelerine bireysel erişimi göremedim.

Sadece sayım için mi?

Yanıtlar:


228

Burada önemli olan şey HashSet<T>isminde: bu bir set . Tek bir setle yapabileceğiniz tek şey, üyelerinin ne olduğunu belirlemek ve bir öğenin üye olup olmadığını kontrol etmektir.

Tek bir öğeyi geri getirip getiremeyeceğinizi sormak (örneğin set[45]), küme kavramını yanlış anlamaktır. Bir kümenin 45. öğesi diye bir şey yoktur. Bir setteki öğelerin sıralaması yoktur. {1, 2, 3} ve {2, 3, 1} setleri her açıdan aynıdır çünkü aynı üyeliğe sahiptirler ve önemli olan üyeliktir.

A üzerinde yinelemek biraz tehlikelidir HashSet<T>çünkü bunu yapmak setteki öğelere bir düzen getirir. Bu emir gerçekten setin bir özelliği değil. Ona güvenmemelisin. Bir koleksiyondaki öğelerin sıralanması sizin için önemliyse, bu koleksiyon bir set değildir.

Setler gerçekten sınırlı ve benzersiz üyelere sahip. Öte yandan, gerçekten hızlılar.


1
Çerçevenin bir SortedSetveri yapısı sağlaması, siparişin bir kümenin özelliği olmadığı konusunda söylediklerinizle çelişir veya geliştirme ekibinin yanlış anlamasına işaret eder.
Veverke

10
Sanırım içindeki öğelerin sırasının HashSettanımlanmadığını söylemenin daha doğru olacağını düşünüyorum , bu nedenle yineleyicinin sırasına güvenmeyin. Setteki öğelere karşı bir şeyler yaptığınız için seti yinelerseniz, bu, siparişle ilgili herhangi bir şeye güvenmediğiniz sürece tehlikeli değildir . A , artı sıranın tüm özelliklerine sahiptir , ancak bundan türetilmez ; Yeniden ifade edilen SortedSet, farklı nesnelerin sıralı bir koleksiyonudur . SortedSetHashSet SortedSetHashSet
Kit

110

İşte kullandığım yerin gerçek bir örneği HashSet<string>:

UnrealScript dosyaları için sözdizimi vurgulayıcımın bir kısmı, Doxygen tarzı yorumları vurgulayan yeni bir özelliktir . Gri (geçerli) veya kırmızı (geçersiz) olarak gösterilip gösterilmeyeceğini belirlemek için bir @veya \komutunun geçerli olup olmadığını anlayabilmem gerekiyor. Bir var HashSet<string>bir isabet Dolayısıyla her tüm geçerli komutların @xxxlexer içinde belirteci, kullandığım validCommands.Contains(tokenText)benim O (1) geçerliliğinin kontrol olarak. Geçerli komutlar setinde komutun varlığı dışında hiçbir şey umurumda değil . Karşılaştığım alternatiflere bakalım:

  • Dictionary<string, ?>: Değer için ne tür kullanıyorum? Sadece kullanacağım için değer anlamsız ContainsKey. Not: .NET 3.0'dan önce bu, O (1) aramaları için tek seçimdi - HashSet<T>3.0 için eklenmiş ve ISet<T>4.0 için uygulanacak şekilde genişletilmiştir .
  • List<string>: Listeyi sıralı tutarsam, BinarySearchO (log n) 'yi kullanabilirim (yukarıda bahsedilen bu gerçeği görmedim). Bununla birlikte, geçerli komutlar listem hiçbir zaman değişmeyen sabit bir liste olduğundan, bu hiçbir zaman basitçe ...
  • string[]: Yine Array.BinarySearchO (log n) performansı verir. Liste kısaysa, bu en iyi performans gösteren seçenek olabilir. Her zaman daha az boşluk yüke sahiptir HashSet, Dictionaryya da List. Bununla bile BinarySearch, büyük setler için daha hızlı değil, ancak küçük setler için denemeye değer. Benimkinde birkaç yüz parça var, bu yüzden bunu geçtim.

24

A arayüzü HashSet<T>uygular ICollection<T>:

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; }
}

Bir List<T>takma IList<T>uzanır,ICollection<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; }
}

Bir HashSet, dahili olarak bir hashtable aracılığıyla uygulanan anlamsallık ayarlamıştır:

Küme, yinelenen öğeler içermeyen ve öğeleri belirli bir sırada olmayan bir koleksiyondur.

HashSet indeks / konum / liste davranışını kaybederse ne kazanır?

HashSet'ten öğe eklemek ve almak her zaman nesnenin kendisi tarafından yapılır, bir indeksleyici aracılığıyla değil ve bir O (1) işlemine yakındır (Liste, O (1) add, O (1) indekse göre geri getir, O (n) bul /Kaldırmak).

Bir HashSet'in davranışı, a'yı Dictionary<TKey,TValue>yalnızca değerler olarak anahtarlar ekleyerek / kaldırarak ve sözlük değerlerini göz ardı ederek karşılaştırılabilir. Bir sözlükteki anahtarların yinelenen değerlere sahip olmamasını beklersiniz ve bu, "Ayar" kısmının amacıdır.


14

Performans, Liste yerine HashSet'i seçmek için kötü bir neden olacaktır. Bunun yerine, amacınızı daha iyi ne yakalayabilir? Sıra önemliyse, Set (veya HashSet) çıktı. Kopyalara izin veriliyorsa, aynı şekilde. Ancak düzen umurumuzda olmadığında pek çok durum vardır ve kopyaları olmamasını tercih ederiz - ve işte o zaman bir Set istersiniz.


21
Performance would be a bad reason to choose HashSet over List: Sadece sana katılmıyorum. Bu, iki Liste yerine bir Dictionray seçmenin performansa yardımcı olmadığını söyler. Şu makaleye
Oscar Mederos

11
@Oscar: Setlerin daha hızlı olmadığını söylemedim - bunun onları seçmek için kötü bir temel olacağını söyledim. Sıralı bir koleksiyonu temsil etmeye çalışıyorsanız, bir set işe yaramaz ve onu içine çekmeye çalışmak bir hata olur; İstediğiniz koleksiyonun düzeni yoksa, bir set mükemmel ve hızlıdır. Ama önemli olan ilk soru: neyi temsil etmeye çalışıyorsun?
Carl Manaster

2
Ama bir düşünün. Verilen dizelerin teknik olarak 10.000 dizeden oluşan bir koleksiyonun üyesi olup olmadığını kontrol etmeye devam etmek string[].Containsve HashSet<string>.Containsamacınızı eşit derecede iyi ifade etmek istiyorsanız; HashSet'i seçmenin nedeni, çok daha hızlı çalışmasıdır.
Casey

12

HashSet, hashing ile uygulanan bir settir . Küme, yinelenen öğeler içermeyen bir değerler koleksiyonudur. Bir kümedeki değerler de genellikle sırasızdır. Yani hayır, bir küme bir listeyi değiştirmek için kullanılamaz (ilk etapta bir küme kullanmanız gerekmedikçe).

Bir setin ne için iyi olabileceğini merak ediyorsanız: Açıkçası, kopyalardan kurtulmak istediğiniz her yerde. Biraz kurgulanmış bir örnek olarak, diyelim ki bir yazılım projesinin 10.000 revizyonunun bir listesi var ve bu projeye kaç kişinin katkıda bulunduğunu öğrenmek istiyorsunuz. A kullanabilir Set<string>ve revizyonlar listesi üzerinde yineleyebilir ve her revizyonun yazarını sete ekleyebilirsiniz. Yinelemeyi bitirdikten sonra, aradığınız cevap setin boyutudur.


Ancak Set, tek tek öğelerin alınmasına izin vermiyor mu? [45] seti gibi mi?
Joan Venge

2
Bunun için, setteki üyeler üzerinde yinelersiniz. Diğer tipik işlemler, setin bir öğe içerip içermediğini kontrol etmek veya setin boyutunu almaktır.
earl

11

HashSet, bir IEnumerable koleksiyonundaki yinelenen öğeleri kaldırmak için kullanılır. Örneğin,

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

bu kodlar çalıştırıldıktan sonra, uniqueStrings {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"} tutar;


6

Muhtemelen hashsetlerin en yaygın kullanımı, dahil etme kontrolü O ( n) (ve O (log n) olduğu sıralı kümeler). Bu nedenle, bir öğenin bir listede yer alıp almadığını çok sayıda kontrol yaparsanız, hahsetler bir performans artışı olabilir. Yalnızca bunların üzerinde yineleme yaparsanız, çok fazla fark olmayacaktır (tüm küme üzerinde yineleme O (n) 'dir, listelerde olduğu gibi ve karma kümelerin öğe eklerken biraz daha fazla yükü vardır).

Ve hayır, bir seti indeksleyemezsiniz, ki bu zaten mantıklı olmaz çünkü setler sıralı değildir. Bazı öğeler eklerseniz, set hangisinin ilk, hangisinin ikinci olduğunu vb. Hatırlamaz.


Yalnızca bunları yinelerseniz, HashSet yöntemi Listeye kıyasla oldukça fazla bellek kullanımı ekler.
SamuelWarren

5

HashSet<T>.NET çerçevesinde bir matematiksel kümeyi bir nesne olarak temsil edebilen bir veri yapısıdır . Bu durumda, GetHashCodeset öğelerinin eşitliğini karşılaştırmak için karma kodları ( her öğenin sonucu) kullanır.

Bir küme, içinde bulunan aynı elemanın yalnızca bir oluşumuna izin vermesi açısından listeden farklıdır. HashSet<T>sadece falseikinci bir özdeş öğe eklemeye çalışırsanız geri döner . Aslında, O(1)iç veri yapısı basitçe bir hashtable olduğundan, elemanların aranması çok hızlıdır ( zaman).

Hangisini kullanacağınızı merak ediyorsanız , koleksiyonunuzda istenmeyen yinelenen öğeler bulunduğunda sorunlara potansiyel olarak izin verebilecek olsa da, uygun olan bir List<T>yerde kullanmanın HashSet<T>en büyük hata olmadığını unutmayın . Dahası, arama (öğe alma) çok daha etkilidir - ideal olarak O(1)(mükemmel gruplama için) O(n)zaman yerine - ki bu birçok senaryoda oldukça önemlidir.


1
Mevcut bir öğeyi bir sete eklemek bir istisna oluşturmaz. Ekle basitçe yanlış döndürür. Ayrıca: mükemmel bir hash işlevine sahip olmadığınız sürece teknik olarak hash araması O (n) 'dir, O (1) değil. Elbette pratikte, hashing işlevi gerçekten kötü olmadığı sürece, O (1) olduğunu varsayarsınız.
sepp2k

1
@ sepp2k: Evet, bu yüzden bir boole döndürüyor ... Önemli olan, size bildirmesidir. Ve hash yukarı bakma en kötü durumdur O (n) eğer baştan savma korkunçsa - genel olarak O (1) 'e çok daha yakın.
Noldorin

4

List<T>sıralı bilgi kümelerini depolamak için kullanılır. Listedeki elemanların göreceli sırasını biliyorsanız, onlara sabit zamanda erişebilirsiniz. Ancak, bir elemanın listede nerede olduğunu belirlemek veya listede var olup olmadığını kontrol etmek için arama süresi doğrusaldır. Öte yandan, HashedSet<T>saklanan verilerin sırasını garanti etmez ve sonuç olarak öğeleri için sürekli erişim süresi sağlar.

Adından da anlaşılacağı gibi, set semantiğiniHashedSet<T> uygulayan bir veri yapısıdır . Veri yapısı, geleneksel Liste uygulamasıyla verimli bir şekilde yapılamayan set işlemlerini (ör. Birleştirme, Fark, Kesişim) uygulamak için optimize edilmiştir.

Bu nedenle, hangi veri türünü kullanacağınızı seçmek, gerçekten uygulamanızla ne yapmaya çalıştığınıza bağlıdır. Bir koleksiyonda öğelerinizin nasıl sıralandığını umursamıyorsanız ve yalnızca numaralandırmak veya var olup olmadığını kontrol etmek istiyorsanız, kullanın HashSet<T>. Aksi takdirde, List<T>veya başka bir uygun veri yapısı kullanmayı düşünün .


2
Başka bir uyarı: kümeler genellikle bir öğenin yalnızca bir oluşumuna izin verir.
Steve Guidi

1

Kısacası - ne zaman bir Sözlük (veya S'nin T'nin bir özelliği olduğu bir Sözlük) kullanmak istendiğinde, bir HashSet (veya HashSet +, T'ye eşit olan IEquatable uygulayan) düşünmelisiniz.


5
Anahtarı önemsemediğiniz sürece sözlüğü kullanmalısınız.
Hardwareguy

1

Temel amaçlanan senaryoda HashSet<T>, iki koleksiyonda LINQ'nun sağladığından daha spesifik set işlemleri istediğinizde kullanılmalıdır. LINQ yöntemleri gibi Distinct, Union, Intersectve Exceptçoğu durumda yeterlidir, fakat bazen daha ince taneli işlemleri gerekebilir ve HashSet<T>sağlamaktadır:

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

LINQ ve HashSet<T>"örtüşen" yöntemler arasındaki diğer bir fark , LINQ'nun her zaman yeni bir döndürmesi IEnumerable<T>ve HashSet<T>yöntemlerin kaynak koleksiyonunu değiştirmesidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.