2009-05-04 19 views
9

Các tài liệu JDK cho java.lang.String.hashCode()famously nói:Bằng chứng: tại sao việc triển khai java.lang.String.hashCode() khớp với tài liệu của nó?

Mã băm cho một đối tượng String được tính như

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 

sử dụng int số học, nơi s[i] là * i * nhân vật của chuỗi thứ, n là độ dài của chuỗi và ^ biểu thị lũy thừa.

Việc thực hiện tiêu chuẩn của biểu thức này là:

int hash = 0; 
for (int i = 0; i < length; i++) 
{ 
    hash = 31*hash + value[i]; 
} 
return hash; 

Nhìn vào này làm cho tôi cảm thấy như tôi đang ngủ qua khóa học các thuật toán của tôi. Biểu thức toán học chuyển thành mã ở trên như thế nào?

Trả lời

12

Tôi không chắc liệu bạn có bỏ lỡ vị trí "^ cho biết lũy thừa" (không phải xor) trong tài liệu đó hay không.

Mỗi lần qua vòng lặp, giá trị băm trước đó được nhân với 31 lần nữa trước khi được thêm vào phần tử tiếp theo là value.

Người ta có thể chứng minh những điều này đều bình đẳng bằng cảm ứng, nhưng tôi nghĩ một ví dụ có thể là hơn rõ ràng:

Nói rằng chúng tôi đang làm việc với một chuỗi 4 char.Hãy cuộn vòng lặp:

hash = 0; 
hash = 31 * hash + value[0]; 
hash = 31 * hash + value[1]; 
hash = 31 * hash + value[2]; 
hash = 31 * hash + value[3]; 

Bây giờ kết hợp các thành một tuyên bố bằng cách thay thế mỗi giá trị của băm vào tuyên bố sau:

hash = 31 * (31 * (31 * (31 * 0 + value[0]) + value[1]) + value[2]) 
    + value[3]; 

31 * 0 là 0, vì vậy đơn giản hóa:

hash = 31 * (31 * (31 * value[0] + value[1]) + value[2]) 
    + value[3]; 

Bây giờ nhân hai từ bên trong với số thứ hai 31:

hash = 31 * (31 * 31 * value[0] + 31 * value[1] + value[2]) 
    + value[3]; 

Bây giờ nhân ba thuật ngữ bên trong bằng cách đó đầu tiên 31:

hash = 31 * 31 * 31 * value[0] + 31 * 31 * value[1] + 31 * value[2] 
    + value[3]; 

và chuyển đổi sang số mũ (không thực sự Java nữa):

hash = 31^3 * value[0] + 31^2 * value[1] + 31^1 * value[2] + value[3]; 
+0

RE câu đầu tiên của bạn: Bạn có thấy một số bằng chứng cho thấy câu hỏi hoặc câu trả lời cụ thể là giả định xor? –

+0

Bạn đã thể hiện sự nhầm lẫn về cách mã và tài liệu có thể tương đương. Vì tài liệu đang sử dụng "^" cho lũy thừa, nhưng Java thường sử dụng nó để có nghĩa là xor bit, tôi tự hỏi nếu đó là nguồn gốc của sự nhầm lẫn của bạn. (Không có câu trả lời nào khác khi tôi bắt đầu viết câu trả lời của tôi, BTW) –

+0

Ahh, tôi hiểu rồi. Không, tôi đã nhận thức được rằng đó là lũy thừa, nhưng không rõ ràng về cách thực hiện theo sau biểu thức toán học. Câu trả lời của bạn làm rõ rằng rất nhiều - nhưng biết viết mã đó chỉ cho biểu thức đó vẫn là một bước nhảy vọt đối với tôi. Để đến được đoạn mã đó, có vẻ như bạn phải viết ra một ví dụ nhỏ, nhận ra rằng bạn có thể "nhân với 0 một cách thông minh" trong việc làm tổ trong cùng để hoàn thành mẫu, sau đó tạo thành vòng lặp. –

24

bỏ vòng lặp. Sau đó, bạn nhận được:

int hash = 0; 

hash = 31*hash + value[0]; 
hash = 31*hash + value[1]; 
hash = 31*hash + value[2]; 
hash = 31*hash + value[3]; 
... 
return hash; 

Bây giờ bạn có thể làm một số thao tác toán học, cắm 0 cho giá trị băm ban đầu:

hash = 31*(31*(31*(31*0 + value[0]) + value[1]) + value[2]) + value[3])... 

Đơn giản hóa nó một số chi tiết:

hash = 31^3*value[0] + 31^2*value[1] + 31^1*value[2] + 31^0*value[3]... 

Và đó là lý về cơ bản các thuật toán ban đầu được đưa ra.

+0

Bạn có thể muốn giải thích nó dưới dạng dạng gán đơn lẻ (SSA), sau đó loại bỏ sự cần thiết phải suy nghĩ về giá trị "băm" có tại bất kỳ thời điểm nào. :-) –

+0

Có vẻ như thuật toán ban đầu cho biết cần: 31^3 * giá trị [0] + 31^2 * giá trị [1] + 31^1 * giá trị [2] + ... Hay chỉ là bộ não chiên của tôi không hài lòng? – Adnan

+0

Thực ra, bạn chính xác, tôi sẽ thực hiện chỉnh sửa. – CookieOfFortune

9

Hãy xem vài lần lặp đầu tiên và bạn sẽ thấy khi bắt đầu mô hình xuất hiện:

 
hash0 = 0 + s0 = s0 
hash1 = 31(hash0) + s1 = 31(s0) + s1 
hash2 = 31(hash1) + s2 = 31(31(s0) + s1) + s2 = 312(s0) + 31(s1) + s2 
... 
+1

<3 Cảm ơn (nhiều hơn hoặc ít hơn) viết câu trả lời của CookieOfFortune ở dạng SSA. Nhiều đánh giá cao! –

+0

Bạn làm cách nào để đăng ký? – CookieOfFortune

+0

Thậm chí sẽ tốt hơn nếu bạn có thể căn chỉnh theo chiều dọc tất cả các từ tương ứng và phân phối 31 (...) trong dòng thứ ba. –

10

Proof bằng cảm ứng:

T1(s) = 0 if |s| == 0, else s[|s|-1] + 31*T(s[0..|s|-1]) 
T2(s) = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 
P(n) = for all strings s s.t. |s| = n, T1(s) = T2(s) 

Let s be an arbitrary string, and n=|s| 
Base case: n = 0 
    0 (additive identity, T2(s)) = 0 (T1(s)) 
    P(0) 
Suppose n > 0 
    T1(s) = s[n-1] + 31*T1(s[0:n-1]) 
    T2(s) = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] = s[n-1] + 31*(s[0]*31^(n-2) + s[1]*31^(n-3) + ... + s[n-2]) = s[n-1] + 31*T2(s[0:n-1]) 
    By the induction hypothesis, (P(n-1)), T1(s[0:n-1]) = T2(s[0:n-1]) so 
     s[n-1] + 31*T1(s[0..n-1]) = s[n-1] + T2(s[0:n-1]) 
    P(n) 

Tôi nghĩ rằng tôi có nó, và một bằng chứng đã được yêu cầu.

+1

oh snap! Cảm ứng! –

0

Không phải là nó vô dụng ở tất cả để đếm hashcode của chuỗi ra của tất cả các ký tự? Hãy tưởng tượng tên tập tin hoặc tên lớp với đường dẫn đầy đủ của chúng được đưa vào HashSet. Hoặc ai đó sử dụng HashSets của tài liệu Chuỗi thay vì Danh sách vì "HashSet always beats Lists".

tôi sẽ làm một cái gì đó như:

int off = offset; 
char val[] = value; 
int len = count; 

int step = len <= 10 ? 1 : len/10; 

for (int i = 0; i < len; i+=step) { 
    h = 31*h + val[off+i]; 
} 
hash = h 

Tại hashcode cuối cùng là gì khác hơn là một gợi ý.

+0

Bỏ qua một nửa các ký tự trong chuỗi có nghĩa là lưu một chuỗi "đếm chuỗi" vào bảng băm có thể dễ dàng gây ra 100 chuỗi để ánh xạ tới từng giá trị băm. Bỏ qua hơn một nửa nhân vật sẽ làm mọi việc trở nên tồi tệ hơn.Bỏ qua bất kỳ khía cạnh nào của chuỗi cho mục đích băm nhỏ có thể gây ra một hình phạt thực sự rất lớn để đổi lấy một khoản hoàn trả khá nhỏ. – supercat

+0

Đó là cơ bản những gì các nhà thiết kế đầu của java mặc dù. Ban đầu hàm băm chuỗi chỉ lấy một mẫu ký tự khi chuỗi dài hơn 15 ký tự. Cuối cùng nó đã được sửa chữa bởi vì nó bật ra để mang lại hiệu suất băm rất xấu với các chuỗi nhất định (ví dụ: với tập hợp các URL thường trông tương tự): http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622 . Hiệu suất đạt được cho việc không sử dụng toàn bộ chuỗi không thể bù đắp hiệu suất băm tồi tệ hơn nhiều. –

+0

Để làm rõ: loại hiệu suất thứ hai nếu đề cập đến hiệu suất "bảng băm", không phải tốc độ thô của tính toán hàm băm. –

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