2012-01-05 43 views
37

Loại này có vẻ như một câu hỏi noob, nhưng tôi không thể tìm thấy câu trả lời cho câu hỏi này một cách cụ thể.HashSet cho phép chèn mục trùng lặp - C#

Tôi có lớp này:

public class Quotes{ 
    public string symbol; 
    public string extension 
} 

Và đang sử dụng này:

HashSet<Quotes> values = new HashSet<Quotes>(); 

Tuy nhiên tôi có thể thêm Quotes cùng đối tượng nhiều lần. Ví dụ, đối tượng Quotes của tôi có thể có 'symbol' bằng 'A' và 'extension' bằng '= n', và đối tượng Quotes này xuất hiện nhiều lần trong HashSet (xem Hashset thông qua chế độ gỡ lỗi). Tôi đã nghĩ rằng khi gọi

values.Add(new Quotes(symb, ext)); 

với cùng một ký tự và số mũ, 'false' sẽ được trả lại và phần tử sẽ không được thêm vào. Tôi có một cảm giác nó có cái gì đó để làm với so sánh các đối tượng báo giá khi HashSet được thêm một đối tượng mới. Mọi sự trợ giúp sẽ rất được trân trọng!

+0

Có lẽ bạn sẽ muốn nhìn vào Hashtable hoặc thậm chí tốt hơn Dictionary MethodMan

+0

@ jpints14 gì làm bạn băm vào? nội dung chuỗi hoặc vị trí bộ nhớ? (hoặc khác) – Adrian

+0

Bởi "có thể thêm cùng một đối tượng báo giá nhiều lần" bạn có nghĩa là thêm chính xác cùng một trường hợp, hoặc thêm trường hợp giống hệt nhau? –

Trả lời

47

Tôi đoán bạn đang tạo một Quotes mới có cùng giá trị. Trong trường hợp này, chúng không bằng nhau. Nếu chúng được coi là bằng nhau, hãy ghi đè phương thức Equals và GetHashCode.

public class Quotes{ 
    public string symbol; 
    public string extension 

    public override bool Equals(object obj) 
    { 
     Quotes q = obj as Quotes; 
     return q != null && q.symbol == this.symbol && q.extension == this.Extension; 
    } 

    public override int GetHashCode() 
    { 
     return this.symbol.GetHashCode()^this.extension.GetHashCode(); 
    } 
} 
+17

Lưu ý rằng nếu biểu tượng hoặc phần mở rộng có thể là null thì GetHashCode phải xử lý và không bị lỗi. –

+0

Tôi có một kiểm tra trước khi so sánh là cần thiết, nhưng cảm ơn cho các tip – jpints14

+3

Lưu ý rằng đối với các kiểu trường khác với 'string', 'int' hoặc các kiểu giá trị khác hoặc các lớp được đóng dấu, bạn nên sử dụng 'q! = null && q.symbol.Equals (this.symbol) && q.extension.Equals (this.extension) 'thay vì sử dụng' == ', bởi vì' == 'không phải là đa hình (tức là nếu các lớp con định nghĩa toán tử' = = ', lớp cơ sở '' orperator == 'vẫn sẽ được sử dụng, trong khi các lớp con có thể ghi đè * phương thức' .Equals() ', vì vậy lớp con' '.Equals()' sẽ được sử dụng. Ngoài ra, 'hash1^hash2' là một hàm băm kém, vì '" a "," b "' và '" b "," a "', có cùng băm. Hãy ưu tiên một cái gì đó như '(hash1 + 7 * 13)^hash2'. –

19

Tôi đã nghĩ rằng khi gọi values.Add(new Quotes(symb, ext)); với cùng một đối tượng và số lẻ, 'false' sẽ được trả về và phần tử sẽ không được thêm vào.

Đây không phải là trường hợp.

HashSet sẽ sử dụng GetHashCodeEquals để xác định sự bình đẳng của các đối tượng của bạn. Ngay bây giờ, vì bạn không ghi đè các phương thức này trong Quotes, mức độ tham chiếu bình thường của System.Object mặc định sẽ được sử dụng. Mỗi khi bạn thêm một Trích dẫn mới, nó là một cá thể đối tượng duy nhất, do đó, HashSet xem nó như một đối tượng duy nhất.

Nếu bạn ghi đè Object.EqualsObject.GetHashCode, nó sẽ hoạt động như bạn mong đợi.

5

HashSets mục so sánh đầu tiên dựa trên giá trị băm của chúng được tính bằng GetHashCode.
Việc triển khai mặc định trả về mã băm dựa trên chính đối tượng đó (khác nhau giữa mỗi trường hợp).

Chỉ khi các băm giống nhau (rất không thể xảy ra cho các băm dựa trên các cá thể), phương thức Equals được gọi và được sử dụng để so sánh chắc chắn hai đối tượng.

Bạn phải lựa chọn:

  • Thay đổi Quotes đến một struct
  • Ghi đè GetHashCode và Equals trong Quotes

Ví dụ:

public override int GetHashCode() 
{ 
    return (this.symbol == null ? 0 : this.symbol.GetHashCode()) 
    ^(this.extension == null ? 0 : this.extension.GetHashCode()); 
} 
public override bool Equals(object obj) 
{ 
    if (Object.ReferenceEquals(this, obj)) 
     return true; 

    Quotes other = obj as Quotes; 
    if (Object.ReferenceEquals(other, null)) 
     return false; 

    return String.Equals(obj.symbol, this.symbol) 
     && String.Equals(obj.extension, this.extension); 
} 
+2

Bạn cũng cần ghi đè 'Object.Equals' - Hashes không được đảm bảo là duy nhất, do đó cả hai phương thức đều được sử dụng ... –

+0

Yeah - tập trung quá nhiều vào viết câu trả lời đủ nhanh :-D Tôi vừa thêm nó, cảm ơn. – Matthias

+1

mmm - Tôi không nghĩ rằng Object.ReferenceEquals của bạn kiểm tra là khá đúng ...;) Về cơ bản, cách bạn có nó, bất cứ lúc nào "obj" là một đối tượng báo giá, bạn sẽ nói nó không bằng nhau (đó là cách duy nhất nó có thể bằng nhau ...) –

2
Quotes q = new Quotes() { symbol = "GE", extension = "GElec" }; 
values.Add(q); 
values.Add(q); 

.. là thêm cùng một ví dụ hai lần và sẽ trả về false lần thứ hai.

values.Add(new Quotes() { symbol = "GE", extension = "GElec" }); 
values.Add(new Quotes() { symbol = "GE", extension = "GElec" }); 

.. đang thêm hai trường hợp khác nhau có cùng giá trị cho trường công khai.

Như đã đề cập elswhere, trọng Equals và GetHashCode sẽ sửa này:

public class Quotes { 
    public string symbol; 
    public string extension; 

    public override bool Equals(object obj) { 
     if (!(obj is Quotes)) { return false; } 
     return (this.symbol == ((Quotes)obj).symbol) && 
       (this.extension == ((Quotes)obj).extension); 
    } 

    public override int GetHashCode() { 
     return (this.symbol.GetHashCode())^(this.extension.GetHashCode()); 
    } 
} 

Nếu bạn bước gỡ lỗi mã của bạn, bạn sẽ thấy rằng values.Add gọi cả Quotes.Equals và Quotes.GetHashCode.

+0

'^' làm gì trong 'return (this.symbol.GetHashCode())^(this.extension.GetHashCode());'? lần đầu tiên tôi thấy điều này là một lỗi đánh máy? – Niklas

2

Tôi biết điều này là muộn, nhưng tôi gặp phải vấn đề tương tự và thấy hiệu suất không thể chấp nhận trong khi thực hiện câu trả lời được chọn, đặc biệt khi bạn có nhiều bản ghi.

Tôi tìm thấy nó nhanh hơn nhiều để biến điều này thành một quá trình hai bước bằng cách sử dụng Hashset và Tuple và cuối cùng chuyển qua một Chọn.

public class Quotes{ 
    public string symbol; 
    public string extension 
} 

var values = new HashSet<Tuple<string,string>>(); 

values.Add(new Tuple<string,string>("A","=n")); 
values.Add(new Tuple<string,string>("A","=n")); 

// values.Count() == 1 

values.Select (v => new Quotes{ symbol = v.Item1, extension = v.Item2 }); 
+0

Hãy thử so sánh nó với một cách tiếp cận giống như câu trả lời được chấp nhận, nhưng cũng có 'Quotes' thực hiện' IEquatable ', và bạn có thể nhận được kết quả tốt hơn. Kết quả tốt hơn là có khả năng có thể thông qua tinh chỉnh 'GetHashCode()' hơn nữa. –

3

Chỉ muốn khắc phục điều gì đó trong câu trả lời của Kendall (không thể nhận xét vì một lý do lạ).

return this.symbol.GetHashCode()^this.extension.GetHashCode(); 

Lưu ý rằng chức năng xor là một cách va chạm dễ bị đặc biệt của việc kết hợp hai băm, đặc biệt là khi họ đều là cùng loại (kể từ khi mọi đối tượng mà biểu tượng == mở rộng sẽ băm thành 0). Ngay cả khi chúng không cùng loại hoặc không có khả năng tương đương với nhau, đây là thực hành không tốt và việc sử dụng nó có thể gây ra các vấn đề trong các thiết bị khác nhau.

Thay vào đó, nhân một băm với một số nguyên tố nhỏ, và thêm thứ hai, ví dụ:

return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode(); 
Các vấn đề liên quan