Khi nào tôi nên sử dụng loại Hashset <T>?


133

Tôi đang khám phá HashSet<T>loại này, nhưng tôi không hiểu nó đứng ở đâu trong các bộ sưu tập.

Có thể sử dụng nó để thay thế một List<T>? Tôi tưởng tượng hiệu suất của một HashSet<T>tốt hơn, nhưng tôi không thể thấy quyền truy cập cá nhân vào các yếu tố của nó.

Có phải chỉ để liệt kê?

Câu trả lời:


228

Điều quan trọng HashSet<T>là ở ngay trong cái tên: đó là một bộ . Điều duy nhất bạn có thể làm với một bộ duy nhất là thiết lập thành viên của nó là gì và kiểm tra xem một mục có phải là thành viên hay không.

Hỏi nếu bạn có thể truy xuất một phần tử duy nhất (ví dụ set[45]) là hiểu sai khái niệm của tập hợp. Không có thứ gọi là phần tử thứ 45 của một tập hợp. Các mặt hàng trong một bộ không có thứ tự. Các bộ {1, 2, 3} và {2, 3, 1} giống hệt nhau về mọi khía cạnh vì chúng có cùng tư cách thành viên và tư cách thành viên là tất cả vấn đề.

Nó hơi nguy hiểm khi lặp đi lặp lại HashSet<T>bởi vì làm như vậy sẽ áp đặt một thứ tự cho các vật phẩm trong bộ. Lệnh đó không thực sự là một tài sản của tập hợp. Bạn không nên dựa vào nó. Nếu việc đặt hàng các mặt hàng trong bộ sưu tập là quan trọng đối với bạn, thì bộ sưu tập đó không phải là một bộ.

Bộ thực sự hạn chế và với các thành viên độc đáo. Mặt khác, chúng thực sự rất nhanh.


1
Thực tế là khung cung cấp SortedSetcấu trúc dữ liệu mâu thuẫn với những gì bạn nói về thứ tự không phải là một thuộc tính của tập hợp - hoặc chỉ ra sự hiểu lầm từ nhóm phát triển.
Veverke

10
Tôi nghĩ sẽ đúng hơn khi nói rằng thứ tự của các mục trong HashSetkhông được xác định, vì vậy đừng dựa vào thứ tự của trình vòng lặp. Nếu bạn lặp lại tập hợp vì bạn đang làm gì đó chống lại các mục trong tập hợp, điều đó không nguy hiểm trừ khi bạn đang dựa vào bất cứ điều gì liên quan đến đơn hàng. A SortedSetcó tất cả các thuộc tính của thứ tự HashSet cộng , tuy nhiên SortedSetkhông xuất phát từ HashSet; rephrased, Sortedset là một bộ sưu tập các đối tượng riêng biệt .
Bộ

110

Đây là một ví dụ thực tế về nơi tôi sử dụng HashSet<string>:

Một phần cú pháp tô sáng của tôi cho các tệp UnrealScript là một tính năng mới làm nổi bật các nhận xét kiểu Doxygen . Tôi cần có thể biết liệu một @hoặc \lệnh có hợp lệ để xác định xem nó hiển thị màu xám (hợp lệ) hay đỏ (không hợp lệ). Tôi có một HashSet<string>trong số tất cả các lệnh hợp lệ, vì vậy bất cứ khi nào tôi nhấn @xxxmã thông báo trong lexer, tôi sử dụng validCommands.Contains(tokenText)làm kiểm tra tính hợp lệ O (1) của mình. Tôi thực sự không quan tâm đến bất cứ điều gì ngoại trừ sự tồn tại của lệnh trong tập hợp các lệnh hợp lệ. Hãy nhìn vào các lựa chọn thay thế mà tôi phải đối mặt:

  • Dictionary<string, ?>: Loại nào tôi sử dụng cho giá trị? Giá trị này là vô nghĩa vì tôi sẽ sử dụng ContainsKey. Lưu ý: Trước .NET 3.0, đây là lựa chọn duy nhất cho tra cứu O (1) - HashSet<T>đã được thêm cho 3.0 và được mở rộng để triển khai ISet<T>cho 4.0.
  • List<string>: Nếu tôi giữ danh sách được sắp xếp, tôi có thể sử dụng BinarySearch, đó là O (log n) (không thấy thực tế này được đề cập ở trên). Tuy nhiên, vì danh sách các lệnh hợp lệ của tôi là một danh sách cố định không bao giờ thay đổi, nên điều này sẽ không bao giờ phù hợp hơn chỉ đơn giản là ...
  • string[]: Một lần nữa, Array.BinarySearchcho hiệu suất O (log n). Nếu danh sách ngắn, đây có thể là tùy chọn hoạt động tốt nhất. Nó luôn luôn có overhead không gian ít hơn HashSet, Dictionaryhoặc List. Ngay cả với BinarySearch, nó không nhanh hơn đối với các bộ lớn, nhưng đối với các bộ nhỏ, nó đáng để thử nghiệm. Của tôi có vài trăm mặt hàng, vì vậy tôi đã chuyển qua này.

24

A HashSet<T>thực hiện ICollection<T>giao diện:

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

Một List<T>dụng cụ IList<T>, mở rộngICollection<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; }
}

Một Hashset đã thiết lập ngữ nghĩa, được triển khai thông qua hàm băm trong nội bộ:

Một tập hợp là một tập hợp không chứa các phần tử trùng lặp và các phần tử của chúng không theo thứ tự cụ thể.

Hashset đạt được gì, nếu nó mất chỉ số / vị trí / hành vi danh sách?

Việc thêm và truy xuất các mục từ Hashset luôn luôn bằng chính đối tượng, không thông qua bộ chỉ mục và gần với thao tác O (1) (Danh sách là O (1) thêm, truy xuất O (1) theo chỉ mục, tìm O (n) /tẩy).

Hành vi của Hashset có thể được so sánh với việc sử dụng một Dictionary<TKey,TValue>bằng cách chỉ thêm / xóa các khóa làm giá trị và tự bỏ qua các giá trị từ điển. Bạn sẽ mong các khóa trong từ điển không có giá trị trùng lặp và đó là điểm của phần "Đặt".


14

Hiệu suất sẽ là một lý do tồi để chọn Hashset trên Danh sách. Thay vào đó, những gì tốt hơn nắm bắt ý định của bạn? Nếu thứ tự là quan trọng, thì Set (hoặc Hashset) sẽ bị loại. Nếu trùng lặp được cho phép, tương tự như vậy. Nhưng có rất nhiều trường hợp khi chúng tôi không quan tâm đến trật tự và chúng tôi không muốn có các bản sao - và đó là khi bạn muốn có một Bộ.


21
Performance would be a bad reason to choose HashSet over List: Tôi không đồng ý với bạn. Đó là kiểu nói rằng việc chọn một Từ điển thay vì hai Danh sách không giúp ích gì cho hiệu suất. Hãy xem bài viết sau
Oscar Mederos

11
@Oscar: Tôi không nói rằng các bộ không nhanh hơn - Tôi đã nói rằng đó sẽ là một cơ sở tồi để chọn chúng. Nếu bạn đang cố gắng đại diện cho một bộ sưu tập được đặt hàng, một bộ chỉ đơn giản là không hoạt động và sẽ là một sai lầm khi cố gắng mang nó vào; nếu bộ sưu tập bạn muốn không có thứ tự, một bộ là hoàn hảo - và nhanh chóng. Nhưng điều quan trọng là câu hỏi đầu tiên: bạn đang cố gắng thể hiện điều gì?
Carl Manaster

2
Nhưng hãy nghĩ về nó. Nếu bạn muốn tiếp tục kiểm tra xem các chuỗi đã cho có phải là thành viên của một số bộ sưu tập 10.000 chuỗi hay không, về mặt kỹ thuật string[].ContainsHashSet<string>.Containsthể hiện ý định của bạn tốt như nhau; Lý do để chọn Hashset là nó sẽ chạy nhanh hơn nhiều.
Casey

12

Hashset là một tập hợp được thực hiện bằng cách băm. Một tập hợp là một tập hợp các giá trị không chứa các phần tử trùng lặp. Các giá trị trong một tập hợp cũng thường không có thứ tự. Vì vậy, không, một bộ không thể được sử dụng để thay thế một danh sách (trừ khi bạn nên sử dụng một bộ ở vị trí đầu tiên).

Nếu bạn đang tự hỏi một bộ có thể tốt cho cái gì: rõ ràng là bất cứ nơi nào bạn muốn loại bỏ các bản sao, rõ ràng. Như một ví dụ hơi khó hiểu, giả sử bạn có một danh sách 10.000 bản sửa đổi của một dự án phần mềm và bạn muốn tìm hiểu có bao nhiêu người đã đóng góp cho dự án đó. Bạn có thể sử dụng Set<string>và lặp lại danh sách các bản sửa đổi và thêm từng tác giả của bản sửa đổi vào bộ. Khi bạn đã hoàn thành việc lặp lại, kích thước của bộ là câu trả lời bạn đang tìm kiếm.


Nhưng Set không cho phép truy xuất các phần tử đơn? Giống như đặt [45]?
Joan Venge

2
Vì thế, bạn sẽ lặp lại các thành viên trong tập hợp. Các hoạt động thông thường khác là kiểm tra xem tập hợp có chứa phần tử hay lấy kích thước của tập hợp không.
bá tước

10

Hashset sẽ được sử dụng để loại bỏ các phần tử trùng lặp trong bộ sưu tập IEnumerable. Ví dụ,

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

sau khi các mã đó được chạy, uniqueStrings giữ {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};


6

Có lẽ cách sử dụng phổ biến nhất cho các hàm băm là để xem liệu chúng có chứa một phần tử nào đó, gần với thao tác O (1) cho chúng hay không (giả sử hàm băm đủ mạnh), trái ngược với các danh sách kiểm tra bao gồm là O ( n) (và các bộ được sắp xếp mà nó là O (log n)). Vì vậy, nếu bạn thực hiện nhiều kiểm tra, cho dù một mục có trong danh sách nào đó, hahssets có thể là một cải tiến hiệu suất. Nếu bạn chỉ lặp đi lặp lại trên chúng, sẽ không có nhiều sự khác biệt (lặp lại trên toàn bộ là O (n), giống như với danh sách và hàm băm có phần chi phí cao hơn khi thêm các mục).

Và không, bạn không thể lập chỉ mục một bộ, dù sao cũng không có ý nghĩa gì, vì các bộ không được đặt hàng. Nếu bạn thêm một số mặt hàng, bộ sẽ không nhớ cái nào là đầu tiên và thứ hai, v.v.


Nếu bạn chỉ lặp lại trên chúng thì phương thức Hashset sẽ thêm một chút sử dụng bộ nhớ so với Danh sách.
SamuelWarren

5

HashSet<T>là một cấu trúc dữ liệu trong khung .NET có khả năng biểu diễn một tập toán học dưới dạng một đối tượng. Trong trường hợp này, nó sử dụng mã băm ( GetHashCodekết quả của từng mục) để so sánh sự bằng nhau của các phần tử tập hợp.

Một tập hợp khác với một danh sách ở chỗ nó chỉ cho phép một lần xuất hiện của cùng một phần tử có trong nó. HashSet<T>sẽ chỉ trở lại falsenếu bạn cố gắng thêm một yếu tố giống hệt thứ hai. Thật vậy, việc tra cứu các yếu tố rất nhanh chóng ( O(1)thời gian), vì cấu trúc dữ liệu nội bộ chỉ đơn giản là một hashtable.

Nếu bạn đang tự hỏi nên sử dụng cái nào, hãy lưu ý rằng sử dụng một List<T>vị trí phù HashSet<T>hợp không phải là sai lầm lớn nhất, mặc dù nó có thể cho phép các vấn đề trong đó bạn có các mục trùng lặp không mong muốn trong bộ sưu tập của mình. Hơn thế nữa, việc tra cứu (truy xuất vật phẩm) hiệu quả hơn rất nhiều - lý tưởng O(1)(để tạo ra sự hoàn hảo) thay vì O(n)thời gian - điều này khá quan trọng trong nhiều tình huống.


1
Thêm một mục hiện có vào một bộ sẽ không ném ngoại lệ. Thêm sẽ chỉ đơn giản là trả về false. Ngoài ra: tra cứu băm kỹ thuật là O (n), không phải O (1), trừ khi bạn có chức năng băm hoàn hảo. Tất nhiên trong thực tế, bạn sẽ thoát khỏi việc giả sử đó là O (1) trừ khi chức năng băm thực sự xấu.
sepp2k

1
@ sepp2k: Vâng, vì vậy nó trả về một boolean ... Vấn đề là, nó thông báo cho bạn. Và băm tìm kiếm là trường hợp xấu nhất O (n) nếu bạn đang nói xấu - nói chung nó gần với O (1) hơn nhiều.
Noldorin

4

List<T>được sử dụng để lưu trữ các bộ thông tin theo thứ tự. Nếu bạn biết thứ tự tương đối của các yếu tố trong danh sách, bạn có thể truy cập chúng trong thời gian không đổi. Tuy nhiên, để xác định vị trí của một phần tử trong danh sách hoặc để kiểm tra xem phần tử đó có tồn tại trong danh sách hay không, thời gian tra cứu là tuyến tính. Mặt khác, HashedSet<T>không đảm bảo thứ tự của dữ liệu được lưu trữ và do đó cung cấp thời gian truy cập liên tục cho các phần tử của nó.

Như tên ngụ ý, HashedSet<T>là một cấu trúc dữ liệu thực hiện các ngữ nghĩa được đặt . Cấu trúc dữ liệu được tối ưu hóa để triển khai các hoạt động tập hợp (ví dụ: Liên minh, Khác biệt, Giao lộ), không thể được thực hiện hiệu quả bằng cách thực hiện Danh sách truyền thống.

Vì vậy, để chọn loại dữ liệu sẽ sử dụng thực sự phụ thuộc vào những gì bạn đang cố gắng làm với ứng dụng của bạn. Nếu bạn không quan tâm đến cách các yếu tố của bạn được sắp xếp trong một bộ sưu tập và chỉ muốn liệt kê hoặc kiểm tra sự tồn tại, hãy sử dụng HashSet<T>. Mặt khác, xem xét sử dụng List<T>hoặc cấu trúc dữ liệu phù hợp khác.


2
Một cảnh báo khác: các bộ thường chỉ cho phép một lần xuất hiện của một phần tử.
Steve Guidi

1

Nói tóm lại - bất cứ khi nào bạn muốn sử dụng Từ điển (hoặc Từ điển trong đó S là thuộc tính của T) thì bạn nên xem xét Hashset (hoặc Hashset + thực hiện IEquitable trên T tương đương với S)


5
Trừ khi bạn quan tâm đến chìa khóa, thì bạn nên sử dụng từ điển.
Phần cứng

1

Trong kịch bản dự định cơ bản HashSet<T>nên được sử dụng khi bạn muốn các thao tác thiết lập cụ thể hơn trên hai bộ sưu tập so với LINQ cung cấp. Phương pháp LINQ thích Distinct, Union, IntersectExceptlà đủ trong hầu hết các tình huống, nhưng đôi khi bạn có thể cần nhiều hoạt động hạt mịn, và HashSet<T>cung cấp:

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

Một điểm khác biệt giữa HashSet<T>các phương thức LINQ và "chồng chéo" là LINQ luôn trả về một phương thức mới IEnumerable<T>HashSet<T>các phương thức sửa đổi bộ sưu tập nguồn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.