Хэширование неизменяемого словаря в 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())).