Алгоритм хеширования, независимый от порядка

в настоящее время я работаю над библиотекой коллекции для моего пользовательского языка программирования. У меня уже есть несколько типов данных (коллекция, список, карта, набор) и реализации для них (изменяемые и неизменяемые), но то, что мне пока не хватало, было hashCode и equals. Хотя это не проблема для списков, поскольку они являются упорядоченными коллекциями, они играют особую роль для наборов и карт. Два множества считаются равными, если они имеют одинаковый размер и одинаковые элементы, и порядок, в котором наборы поддержание их не должно влиять на их равенство. Из-за контракта equals-hashCode -, также должна отражать это поведение, что означает, что два набора с одинаковыми элементами, но разным порядком должны иметь одинаковый хэш-код. (То же самое относится и к картам, которые технически являются набором пар ключ-значение)

пример (псевдокод):

let set1: Set<String> = [ "a", "b", "c" ]
let set2: Set<String> = [ "b", "c", "a" ]
set1 == set2       // should return true
set1.hashCode == set2.hashCode // should also return true

как бы я реализовал достаточно хороший алгоритм хэша, для которого hashCodes в приведенном выше примере возвращает то же значение?

3 ответов


сам JDK предлагает следующее решение этой проблемы. Договор java.утиль.Set интерфейс гласит:

возвращает значение хэш-кода для этого набора. Хэш-код множества определяется как сумма хэш-кодов элементов в наборе, где хэш-код нулевого элемента определяется как ноль. Это гарантирует, что С1.равно (s2) означает, что s1.hashCode ()==s2.hashCode() для любых двух наборов s1 и s2, как требуется общим договор об объекте.hashCode ().

альтернативой использованию суммы хэш-кодов записей было бы использование, например,^ (XOR) оператор.

язык Scala использует инвариантную к порядку версию Murmurhash (МФ. рядовой scala.util.hashing.MurmurHash3 class) для реализации hashCode (или ##) метод его неизменяемые наборы и подобные коллекции.


вы можете вычислить хэш-сумму сортировки вашей коллекции в алфавитном порядке.

есть образец C# - надеюсь, вы можете перевести его на Java:)

static String GetHash(List<String> l)
{
    using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
    {
        return BitConverter.ToString(md5.ComputeHash(l.OrderBy(p => p).SelectMany(s => System.Text.Encoding.ASCII.GetBytes(s + (char)0)).ToArray())).Replace("-", "");
    }
}

вот псевдокод для возможной реализации:

String hashCode = null;
for(element : elements){
    hashCode = xor(hashCode, getHashCode(element));
}
return hashCode;

на xor функция должна возвращать строку длиной до самого длинного из двух аргументов. Он будет исправлять биты в каждом, пока не дойдет до конца одного из аргументов. Затем он возьмет оставшиеся биты из более длинной строки и добавит их.

эта реализация будет означать, что хэш-код набора будет таким же длинным, как хэш-код его самого длинного элемента. Потому что ты XORing бит, в конце хэш-код будет одинаковым независимо от порядка ваших элементов. Однако, как и при любой реализации хэширования, будет вероятность столкновений.