2009-10-29 29 views
41

Mọi người có thể giới thiệu các cách nhanh chóng và đơn giản để kết hợp mã băm của hai đối tượng. Tôi không quá lo lắng về xung đột vì tôi có một Bảng băm mà sẽ xử lý hiệu quả mà tôi chỉ muốn một cái gì đó mà tạo ra một mã một cách nhanh chóng nhất có thể.Kết hợp mã băm nhanh và đơn giản

Reading xung quanh SO và các trang web có vẻ là một vài ứng cử viên chính:

  1. XORing
  2. XORing với Thủ Nhân
  3. hoạt động số đơn giản như phép nhân/bộ phận (với kiểm tra tràn hoặc quấn xung quanh)
  4. Tạo chuỗi và sau đó sử dụng các lớp Chuỗi Phương thức mã băm

Mọi người sẽ giới thiệu gì và tại sao?

Trả lời

83

Cá nhân tôi sẽ tránh XOR - có nghĩa là bất kỳ hai giá trị bằng nhau sẽ dẫn đến 0 - vì vậy băm (1, 1) == băm (2, 2) == băm (3, 3) v.v ... 5, 0) == băm (0, 5) vv đôi khi có thể xuất hiện. Tôi cố tình sử dụng nó để đặt băm - nếu bạn muốn băm chuỗi các mục và bạn không quan tâm đến việc đặt hàng, nó thật tuyệt.

Tôi thường sử dụng:

unchecked 
{ 
    int hash = 17; 
    hash = hash * 31 + firstField.GetHashCode(); 
    hash = hash * 31 + secondField.GetHashCode(); 
    return hash; 
} 

Đó là hình thức mà Josh Bloch gợi ý trong Java hiệu quả. Lần trước tôi trả lời một câu hỏi tương tự, tôi đã tìm được một bài báo mà ở đó nó đã được thảo luận chi tiết - IIRC, không ai thực sự biết tại sao nó hoạt động tốt, nhưng nó lại có. Nó cũng dễ nhớ, dễ triển khai và dễ mở rộng đến bất kỳ số trường nào.

+0

Vâng, đó là mối quan tâm của tôi về XORing, trong loại dữ liệu tôi ghép nối, nó không thể ghép nối các mục quá bình đẳng nhưng không thể không? – RobV

+3

Trông giống như băm của Dan Bernstein (hoặc Chris Torek), chỉ với các hằng số khác nhau. Không ai biết tại sao nó hoạt động tốt. – ephemient

+0

@RobV: Tôi không muốn phải suy nghĩ nếu tôi không phải làm vậy. Tôi sử dụng băm này ngay cả khi tôi * có thể * lấy đi với XOR, chỉ để tránh phải tự hỏi cho dù đó là an toàn hay không :) –

0

Nếu băm đầu vào của bạn có cùng kích thước, phân bố đồng đều và không liên quan đến nhau thì XOR phải OK. Cộng với nó nhanh.

Tình hình tôi đang đề xuất này là nơi bạn muốn làm

H = hash(A)^hash(B); // A and B are different types, so there's no way A == B. 

tất nhiên, nếu A và B có thể được dự kiến ​​sẽ băm với giá trị cùng với một hợp lý (không đáng kể) xác suất , thì bạn không nên sử dụng XOR theo cách này.

+0

làm cách nào để biết liệu mã băm của tôi có được phân phối đồng đều hay không, liệu có điểm chuẩn dễ dàng để thực hiện việc này không? Tôi biết tỷ lệ va chạm là khá thấp nhưng điều đó có nhất thiết phải tương ứng với một bản phân phối đồng đều không? – RobV

-10

Tôi khuyên bạn nên sử dụng hàm băm dựng sẵn trong System.Security.Cryptography thay vì tự cuộn của riêng bạn.

+8

Không, chúng có mục đích rất khác nhau và phá vỡ quy tắc GetHashCode nên nhanh. –

1

Nếu bạn đang tìm kiếm tốc độ và không có quá nhiều va chạm, thì XOR là nhanh nhất. Để ngăn việc phân cụm quanh 0, bạn có thể thực hiện một việc như sau:

Tất nhiên, một số nguyên mẫu phải cung cấp cho bạn ý tưởng về hiệu suất và phân cụm.

25

Trong khi mẫu được nêu trong câu trả lời của Jon Skeet hoạt động tốt nói chung là họ hàm băm, lựa chọn các hằng số là quan trọng và hạt giống 17 và hệ số 31 như được ghi trong câu trả lời không hoạt động tốt chút nào trường hợp sử dụng phổ biến. Trong hầu hết các trường hợp sử dụng, giá trị được băm gần bằng không hơn int.MaxValue và số lượng mục được băm cùng nhau là vài chục hoặc ít hơn.

Để băm nhỏ một số nguyên tuple {x, y} trong đó -1000 <= x <= 1000-1000 <= y <= 1000, nó có tỷ lệ va chạm quá mức gần 98,5%. Ví dụ: {1, 0} -> {0, 31}, {1, 1} -> {0, 32}, v.v. Nếu chúng tôi mở rộng phạm vi phủ sóng cũng bao gồm các phần tử ở nơi 3 <= n <= 25, điều đó sẽ không tồi tệ hơn với tỷ lệ va chạm khoảng 38%. Nhưng chúng ta có thể làm tốt hơn nhiều.

public static int CustomHash(int seed, int factor, params int[] vals) 
{ 
    int hash = seed; 
    foreach (int i in vals) 
    { 
     hash = (hash * factor) + i; 
    } 
    return hash; 
} 

Tôi đã viết một Monte Carlo vòng lặp tìm kiếm lấy mẫu thử nghiệm các phương pháp trên với các giá trị khác nhau cho hạt giống và yếu tố trên khác nhau ngẫu nhiên n-tuples các số nguyên ngẫu nhiên i. Phạm vi được phép là 2 <= n <= 25 (trong đó n là ngẫu nhiên nhưng thiên về phía cuối thấp hơn của dải ô) và -1000 <= i <= 1000. Ít nhất 12 triệu thử nghiệm va chạm duy nhất được thực hiện cho mỗi cặp hạt và yếu tố.

Sau khoảng 7 giờ chạy, cặp tốt nhất được tìm thấy (trong đó hạt giống và hệ số được giới hạn ở 4 chữ số trở xuống) là: seed = 1009, factor = 9176, với tỷ lệ va chạm là 0,1131%. Trong các khu vực 5 và 6 chữ số, thậm chí còn có các tùy chọn tốt hơn. Nhưng tôi đã chọn biểu diễn 4 chữ số hàng đầu cho ngắn gọn và nó hoạt động khá tốt trong tất cả các trường hợp băm phổ biến intchar. Nó cũng có vẻ làm việc tốt với số nguyên lớn hơn nhiều.

Cần lưu ý rằng "là thủ tướng" dường như không phải là điều kiện tiên quyết chung cho hiệu suất tốt như là một hạt giống và/hoặc yếu tố mặc dù nó có khả năng giúp. 1009 lưu ý ở trên thực tế là nguyên tố, nhưng 9176 thì không. Tôi đã thử nghiệm một cách rõ ràng các biến thể về điều này, nơi tôi đã thay đổi factor thành nhiều số nguyên tố gần 9176 (trong khi rời khỏi seed = 1009) và tất cả chúng đều hoạt động kém hơn giải pháp trên.

Cuối cùng, tôi cũng so sánh với nhóm chức năng giới thiệu chung của ReSharper là hash = (hash * factor)^i; và số CustomHash() ban đầu như đã nêu ở trên có hiệu suất cao hơn rất nhiều. Kiểu dáng ReSharper XOR dường như có tỷ lệ va chạm trong khoảng 20-30% cho các giả định trường hợp sử dụng phổ biến và không nên được sử dụng theo ý kiến ​​của tôi.

+3

Chà. Tôi yêu công việc đã đi vào câu trả lời này. Ấn tượng, được thực hiện tốt! –

10

Tôi đoán rằng đội .NET Framework đã làm một công việc đàng hoàng trong việc kiểm tra thực hiện System.String.GetHashCode() của họ, vì vậy tôi sẽ sử dụng nó:

// System.String.GetHashCode(): http://referencesource.microsoft.com/#mscorlib/system/string.cs,0a17bbac4851d0d4 
// System.Web.Util.StringUtil.GetStringHashCode(System.String): http://referencesource.microsoft.com/#System.Web/Util/StringUtil.cs,c97063570b4e791a 
public static int CombineHashCodes(IEnumerable<int> hashCodes) 
{ 
    int hash1 = (5381 << 16) + 5381; 
    int hash2 = hash1; 

    int i = 0; 
    foreach (var hashCode in hashCodes) 
    { 
     if (i % 2 == 0) 
      hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27))^hashCode; 
     else 
      hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27))^hashCode; 

     ++i; 
    } 

    return hash1 + (hash2 * 1566083941); 
} 

thực hiện khác là từ System.Web.Util.HashCodeCombiner.CombineHashCodes(System.Int32, System.Int32)System.Array.CombineHashCodes(System.Int32, System.Int32) phương pháp. Điều này đơn giản hơn, nhưng có lẽ không có phân phối tốt như phương pháp trên:

// System.Web.Util.HashCodeCombiner.CombineHashCodes(System.Int32, System.Int32): http://referencesource.microsoft.com/#System.Web/Util/HashCodeCombiner.cs,21fb74ad8bb43f6b 
// System.Array.CombineHashCodes(System.Int32, System.Int32): http://referencesource.microsoft.com/#mscorlib/system/array.cs,87d117c8cc772cca 
public static int CombineHashCodes(IEnumerable<int> hashCodes) 
{ 
    int hash = 5381; 

    foreach (var hashCode in hashCodes) 
     hash = ((hash << 5) + hash)^hashCode; 

    return hash; 
} 
1

Sử dụng logic kết hợp trong bộ dữ liệu. Ví dụ này đang sử dụng tuples C# 7.

(field1, field2).GetHashCode(); 
+0

Ý tưởng tuyệt vời mặc dù tôi nghi ngờ rằng điều này có thể có vấn đề với GC churn kể từ khi bạn đang ngầm tạo một đối tượng sống ngắn – RobV

+2

@RobV Tuples là loại giá trị, do đó, chúng được phân bổ và không có áp lực GC. –

Các vấn đề liên quan