Хэширование неизменяемого словаря в Python
короткая версия: каков наилучший алгоритм хеширования для мультисета, реализованного в виде словаря неупорядоченных элементов?
Я пытаюсь хэшировать неизменяемый multiset (который является мешком или multiset на других языках: как математический набор, за исключением того, что он может содержать более одного элемента), реализованный как словарь. Я создал подкласс стандартного класса библиотеки collections.Counter
, подобно совету здесь:Python hashable dicts, которым рекомендует хэш-функцию следующим образом:
class FrozenCounter(collections.Counter):
# ...
def __hash__(self):
return hash(tuple(sorted(self.items())))
создание полного кортежа элементов занимает много памяти (относительно, скажем, с помощью генератора), и хэширование будет происходить в чрезвычайно интенсивной памяти части моего приложения. Что еще более важно, мои ключи словаря (элементы multiset), вероятно, не будут упорядочены.
Я думаю использовать этот алгоритм:
def __hash__(self):
return functools.reduce(lambda a, b: a ^ b, self.items(), 0)
Я полагаю, что использование побитового XOR означает заказ не имеет значения для хэш-значения в отличие от хеширования кортежа? Я полагаю, что я мог бы полу-реализовать alogrithm кортежа Python на неупорядоченном потоке кортежей моих данных. См.https://github.com/jonashaag/cpython/blob/master/Include/tupleobject.h (поиск на странице слова "хэш") - но я едва знаю достаточно C, чтобы прочитать его.
мысли? Предложения? Спасибо.
(Если вам интересно, почему я путаюсь с попыткой хэша multiset: входные данные для моей проблемы-это наборы мультисетей, и в каждом наборе мультисетей каждый мультисет должен быть уникальным. Я работаю над крайним сроком, и я не опытный кодер, поэтому я хотел избежать изобретения новых алгоритмов, где это возможно. Кажется, самый Питонический способ убедиться, что у меня есть уникальная куча вещей, - это поместить их в
set()
, но вещи должны быть hashable.)
что я собрал из комментариев
оба @marcin и @senderle дал почти тот же ответ: используйте hash(frozenset(self.items()))
. Это имеет смысл, потому что items()
"вид" устанавливаются как. @marcin был первым, но я дал галочку @senderle из-за хорошего исследования времени работы big-O для разных решений. @marcin также напоминает мне включить __eq__
метод -- но тот, который унаследован от dict
будет работать нормально. Вот как я реализую все-дальнейшие комментарии и предложения, основанные на этом коде добро пожаловать:
class FrozenCounter(collections.Counter):
# Edit: A previous version of this code included a __slots__ definition.
# But, from the Python documentation: "When inheriting from a class without
# __slots__, the __dict__ attribute of that class will always be accessible,
# so a __slots__ definition in the subclass is meaningless."
# http://docs.python.org/py3k/reference/datamodel.html#notes-on-using-slots
# ...
def __hash__(self):
"Implements hash(self) -> int"
if not hasattr(self, '_hash'):
self._hash = hash(frozenset(self.items()))
return self._hash
2 ответов
поскольку словарь является неизменяемым, вы можете создать хэш при создании словаря и вернуть его напрямую. Мое предложение было бы создать frozenset
С items
(в 3+; iteritems
в 2.7), хэшировать его и хранить хэш.
чтобы предоставить явный пример:
>>>> frozenset(Counter([1, 1, 1, 2, 3, 3, 4]).iteritems())
frozenset([(3, 2), (1, 3), (4, 1), (2, 1)])
>>>> hash(frozenset(Counter([1, 1, 1, 2, 3, 3, 4]).iteritems()))
-3071743570178645657
>>>> hash(frozenset(Counter([1, 1, 1, 2, 3, 4]).iteritems()))
-6559486438209652990
чтобы уточнить, почему я предпочитаю frozenset
к кортежу отсортированных элементов: a frozenset
Не нужно сортировать элементы (потому что они стабильно упорядочены по их хэшу в памяти), и поэтому начальный хэш должен завершиться в O(n) времени, а не O (N log n) времени. Это можно увидеть из frozenset_hash
и set_next
реализаций.
вы рассматривали hash(sorted(hash(x) for x in self.items()))
? Таким образом, вы сортируете только целые числа и не должны создавать список.
вы также можете хешировать элементы вместе, но, честно говоря, я не знаю, насколько хорошо это будет работать (у вас будет много столкновений?). Говоря о столкновениях, вам не нужно реализовывать __eq__
способ?
альтернативно, похоже на мой ответ здесь, hash(frozenset(self.items()))
.