Я хочу создать структуру данных, которая может принимать пару ключей (строки) и возвращать значение (также строку). Я хотел бы вернуть одно и то же значение независимо от порядка, в котором введены 2 ключа (например, data [key1] [key2] возвращает то же значение, что и data [key2] [key1]). Есть ли термин / понятие для этого описания?

Моя текущая реализация заключается в создании двумерного словаря, подобного этому:

my_dict = {'key1': {'key1': None,
                    'key2': 'foo',
                    ...
                    'keyn': 'bar'},
           'key2': {'key1': 'foo',
                    'key2': None,
                    ...
                    'keyn': 'baz'},
           ...
           'keyn': {'key1': 'bar',
                    'key2': 'baz',
                    ...
                    'keyn': None}}

# Calling my_dict['key1']['key2'] and my_dict['key2']['key1'] both return 'foo', which is what I want and expect.

Это не кажется мне правильным. Я дублирую данные и создаю n * n записей, когда мне нужно только (n * (n - 1)) / 2.

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

my_dict = {('key1', 'key2'): 'foo'}

Но это не работает, так как вызов my_dict[('key2', 'key1')] дает мне KeyError

Один из обходных путей для 1D-словаря кортежей - создать попытку / исключение.

def get_value(my_dict, key1, key2):
    try:
        return my_dict[key1][key2]
    except KeyError:
        return my_dict[key2][key1]

Это не кажется интуитивно понятным и больше похоже на «пластырь» проблемы.

Один из методов, который я не тестировал, - это одномерный словарь, в котором ключ использует экземпляр пользовательского класса, который содержит key1 и key2 в качестве атрибутов. Чтобы сделать это, объект должен быть неизменным и хешируемым, где хеш-функция будет использовать атрибуты объекта и создавать один и тот же «ключ хеша» независимо от порядка атрибутов. Я никогда не делал этого раньше и не знаю, как это сделать. Это правильный путь? Я чувствую себя очень глупо, что я не смог понять это, так как кажется, что есть простой ответ на это.

3
koreebay 19 Дек 2015 в 02:55

4 ответа

Лучший ответ

Если вы хотите, чтобы ключи сравнивались одинаково независимо от порядка, вы можете использовать frozensets в качестве ключей, которые соответствуют вашей идее о пользовательском классе:

my_dict = {frozenset(['key1', 'key2']): 'foo'}

Не имеет значения, в каком порядке вы добавляете ключи:

In [44]: my_dict = {frozenset(['key1', 'key2']): 'foo'}

In [45]: k = frozenset(["key1","key2"])

In [46]: k2 = frozenset(["key2","key1"])

In [47]: my_dict[k]
Out[47]: 'foo'

In [48]: my_dict[k2]
Out[48]: 'foo'

Вы можете иметь столько значений в frozenset, сколько хотите, чтобы они все равно сравнивались, использование замороженного набора также эффективно для поиска:

In [55]: timeit my_dict[k]
10000000 loops, best of 3: 103 ns per loop

In [56]: timeit get_value(my_dict, 'key1', 'key2')
1000000 loops, best of 3: 455 ns per loop

In [57]: timeit get_value(my_dict, 'key2', 'key1')
1000000 loops, best of 3: 455 ns per loop

Даже сроки создания замороженного объекта и поиска двух элементов быстрее:

In [5]: my_dict = {frozenset(['key1', 'key2']): 'foo'}

In [6]: timeit my_dict[frozenset(["key1","key2"])]
1000000 loops, best of 3: 380 ns per loop

Всего за 3 строки у вас есть 3! Пермь, чтобы проверить, для 6 у вас есть 720, так что для чего-то большего, чем проверка пары, каждая возможная перестановка не является реалистичной или удаленно эффективной.

2
Padraic Cunningham 19 Дек 2015 в 00:43

Вы можете использовать hashable объект, как вы предлагаете. Для этого вам необходимо реализовать методы __hash__ и __eq__ или __cmp__ (один из двух), например:

class Key:

   def __init__(self, key1, key2):
      self.key1 = key1
      self.key2 = key2

   def __hash__(self):

      # XORing two hash values is usually fine. Besides, the operation is symmetric, which is what you want
      return hash(self.key1) ^ hash(self.key2)

   def __eq__(self, other):

      if self == other:
         return True

      if self.key1 == other.key1 and self.key2 == other.key2:
         return True

      if self.key1 == other.key2 and self.key2 == other.key1:
         return True

      return False
1
André Fratelli 19 Дек 2015 в 00:08

Как насчет

my_dict = {('key1', 'key2'): 'foo'}

def get_value(my_dict, key1, key2):
    return my_dict.get((key2, key1) , my_dict.get((key1, key2)))

Таким образом, вы должны сделать меньше записей, и это лучше, чем try/except

Примере

In [11]: my_dict = {('key1', 'key2'): 'foo'}

In [12]: def get_value(my_dict, key1, key2):
   ....:     return my_dict.get((key2, key1) , my_dict.get((key1, key2)))

In [13]: get_value(my_dict, 'key1', 'key2')
Out[13]: 'foo'
0
Yash Mehrotra 19 Дек 2015 в 00:09

Вот что я нашел. Размеры списков должны быть одинаковыми.

my_dict = {}
sub_dict = {}

ks = ['key1','key2','key3','keyn']
kks = ['key1','key2','key3','keyn']
vals = [None,'foo','bar','baz']

for val in vals:    
    for kk in kks:
        sub_dict[kk] = val

print sub_dict

for k in ks:
    my_dict[k] = sub_dict

print my_dict

Фрозенет, наверное, лучше.

0
Clay Wahlstrom 19 Дек 2015 в 01:21