Словарь с множественными ключами, где порядок ключей не имеет значения

Я пытаюсь создать словарь с двумя строками в качестве ключа, и я хочу, чтобы ключи находились в любом порядке.

myDict[('A', 'B')] = 'something'
myDict[('B', 'A')] = 'something else'
print(myDict[('A', 'B')])

Я хочу, чтобы этот фрагмент кода печатал "что-то еще". К сожалению, кажется, что упорядочение имеет дело с кортежами. Какая будет лучшая структура данных для использования в качестве ключа?

Ответ 1

Используйте frozenset

Вместо tuple, который упорядочен, вы можете использовать frozenset, который является неупорядоченным, но все еще хешируется, поскольку frozenset является неизменным.

myDict = {}
myDict[frozenset(('A', 'B'))] = 'something'
myDict[frozenset(('B', 'A'))] = 'something else'
print(myDict[frozenset(('A', 'B'))])

Что будет печатать:

something else


К сожалению, эта простота имеет недостаток, поскольку frozenset - это в основном "замороженный" набор. Не будет повторяющихся значений в frozenset, например,

frozenset((1, 2)) == frozenset((1,2,2,1,1))

Если обрезка значений не беспокоит вас, не стесняйтесь использовать frozenset

Но если вы на 100% уверены, что не хотите того, что было упомянуто выше, есть, однако, две альтернативы:


Первый способ - использовать Counter и сделать его hashable с помощью frozenset снова: (Примечание: все в кортеже должно быть хешируемым)

from collections import Counter

myDict = {}
myDict[frozenset(Counter(('A', 'B')).items())] = 'something'
myDict[frozenset(Counter(('B', 'A')).items())] = 'something else'
print(myDict[frozenset(Counter(('A', 'B')).items())])

# something else

Второй способ - использовать встроенную функцию sorted и сделать ее хешируемой, сделав ее tuple. Это будет сортировать значения перед использованием в качестве ключа: (Примечание: все в кортеже должно быть сортируемым и хешируемым)

myDict = {}
myDict[tuple(sorted(('A', 'B')))] = 'something'
myDict[tuple(sorted(('B', 'A')))] = 'something else'
print(myDict[tuple(sorted(('A', 'B')))])

# something else

Но если элементы кортежа не являются ни хэшируемыми, ни все они не сортируемы, к сожалению, вам может быть не повезло и вам нужно создать свою собственную структуру dict... D:

Ответ 2

Вы можете создать свою собственную структуру:

class ReverseDict:
   def __init__(self):
      self.d = {}
   def __setitem__(self, k, v):
      self.d[k] = v

   def __getitem__(self, tup):
      return self.d[tup[::-1]]

myDict = ReverseDict()
myDict[('A', 'B')] = 'something'
myDict[('B', 'A')] = 'something else'
print(myDict[('A', 'B')])

Вывод:

something else

Ответ 3

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

class UnorderedKeyDict(dict):
    def __init__(self, *arg):
        if arg:
            for k,v in arg[0].items():
                self[k] = v

    def _hash(self, tup):
        return sum([hash(ti) for ti in tup])

    def __setitem__(self, tup, value):
        super().__setitem__(self._hash(tup), value)

    def __getitem__(self, tup):
      return super().__getitem__(self._hash(tup))

mydict = UnorderedKeyDict({('a','b'):12,('b','c'):13})
mydict[('b','a')]
    >> 12