Доказательство: почему реализация java.lang.String.hashCode () соответствует его документации?

StackOverflow https://stackoverflow.com/questions/822363

  •  03-07-2019
  •  | 
  •  

Вопрос

Документация JDK для < code> java.lang.String.hashCode () классно говорит:

  

Хеш-код для объекта String вычисляется как

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

используя арифметику int , где s [i] - это * i * -й символ строки, n - длина строки, а ^ обозначает возведение в степень.

Стандартная реализация этого выражения:

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

Глядя на это, я чувствую, что сплю в курсе алгоритмов. Как это математическое выражение переводится в приведенный выше код?

Это было полезно?

Решение

Я не уверен, что вы пропустили, где написано, что " ^ означает возведение в степень " (не xor) в этой документации.

Каждый раз в цикле предыдущее значение хеша умножается на 31 снова перед добавлением к следующему элементу value .

Можно доказать, что эти вещи равны по индукции, но я думаю, что пример может быть более ясно:

Скажем, мы имеем дело со строкой из 4 символов. Давайте развернем цикл:

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

Теперь объедините их в один оператор, подставив каждое значение хэша в следующий оператор:

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

31 * 0 равно 0, поэтому упростите:

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

Теперь умножьте два внутренних члена на эти вторые 31:

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

Теперь умножьте три внутренних члена на эти первые 31:

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

и преобразовать в экспоненты (больше не Java):

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

Другие советы

разверните цикл. Тогда вы получите:

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;

Теперь вы можете сделать некоторые математические манипуляции, вставьте 0 для начального значения хеша:

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

Упростите это еще немного:

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

И это по сути исходный алгоритм.

Доказательство по индукции:

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)

Я думаю, что у меня есть, и было запрошено доказательство.

Посмотрите на первые несколько итераций, и вы увидите, что шаблон начинает появляться:

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
...

Разве не бесполезно считать хеш-код String из всех символов ? Представьте себе имена файлов или имена классов с их полным путем, помещенным в HashSet. Или кто-то, кто использует документы HashSets of String вместо списков, потому что HashSet всегда бьет списки " .

Я бы сделал что-то вроде:

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

В конце хеш-код - не более чем подсказка.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top