Хеширование неизменяемого словаря в Python

Краткая версия: Какой лучший алгоритм хэширования для мультимножества реализован как словарь неупорядоченных элементов?

Я пытаюсь хешировать неизменяемый мультимножество (это мешок или мультимножество на других языках: как математический набор, за исключением того, что он может содержать более одного элемента), реализованный в качестве словаря. Я создал подкласс стандартного класса библиотеки collections.Counter, аналогичный приведенному здесь совету: Python hashable dicts, который рекомендует такую ​​хэш-функцию:

class FrozenCounter(collections.Counter):
    # ...
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

Создание полного набора элементов занимает много памяти (относительно, скажем, с использованием генератора), и хеширование будет происходить в чрезвычайно интенсивной части приложения. Что еще более важно, мои словарные ключи (многоэлементные элементы), вероятно, не будут упорядочены.

Я думаю об использовании этого алгоритма:

def __hash__(self):
    return functools.reduce(lambda a, b: a ^ b, self.items(), 0)

Я полагаю, что использование побитового XOR означает, что порядок не имеет значения для хеш-значения, в отличие от хэширования кортежа? Предполагаю, что я мог бы полу-реализовать алограмм арифметического кортежа Python в неупорядоченном потоке кортежей моих данных. См. https://github.com/jonashaag/cpython/blob/master/Include/tupleobject.h (поиск на странице для слова "хэш" ) - но я едва знаю достаточно C, чтобы прочитать его.

Мысли? Предложения? Спасибо.


(Если вам интересно, почему я возился с попыткой хеширования мультимножества: входные данные для моей проблемы - это множества мультимножеств, и в каждом наборе мультимножеств каждый мультимножество должен быть уникальным. Я работаю над крайним сроком и я не опытный кодер, поэтому я хотел избежать возможного придумывания новых алгоритмов. Похоже, что самый питоновский способ убедиться, что у меня есть уникальная куча вещей, это поместить их в set(), но вещи должны быть хешируемыми.)

Что я собрал из комментариев

Оба @marcin и @senderle дали почти такой же ответ: используйте hash(frozenset(self.items())). Это имеет смысл, потому что items() "views" установлены как. @marcin был первым, но я дал галочку @senderle из-за хороших исследований в течение большого времени работы 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

Ответы

Ответ 1

Поскольку словарь неизменен, вы можете создать хэш, когда словарь создан и вернуть его напрямую. Мое предложение состояло в том, чтобы создать 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.

Ответ 2

Вы считали hash(sorted(hash(x) for x in self.items()))? Таким образом, вы только сортируете целые числа и не должны создавать список.

Вы также можете объединить хэш элементов, но, честно говоря, я не знаю, как хорошо это работает (у вас было бы много столкновений?). Говоря о столкновениях, вам не нужно внедрять метод __eq__?

В качестве альтернативы, как и мой ответ здесь, hash(frozenset(self.items())).