Алгоритм хеширования, независимый от порядка
в настоящее время я работаю над библиотекой коллекции для моего пользовательского языка программирования. У меня уже есть несколько типов данных (коллекция, список, карта, набор) и реализации для них (изменяемые и неизменяемые), но то, что мне пока не хватало, было 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
как бы я реализовал достаточно хороший алгоритм хэша, для которого hashCode
s в приведенном выше примере возвращает то же значение?
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 бит, в конце хэш-код будет одинаковым независимо от порядка ваших элементов. Однако, как и при любой реализации хэширования, будет вероятность столкновений.