二维字典或其他数据结构,其中键的顺序无关紧要

时间:2015-12-18 23:55:42

标签: python dictionary data-structures

我想创建一个数据结构,它可以接受一对键(字符串)并返回一个值(字符串也是如此)。无论输入2个键的顺序如何,我都希望返回相同的值(例如,data [key1] [key2]返回与data [key2] [key1]相同的值)。这个描述有一个术语/概念吗?

我目前的实现是创建一个像这样的2D字典:

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 - 1))/ 2时,我正在创建n * n个条目。

所以,我尝试创建一个字典,其中键是一个元组:

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

但这不起作用,因为调用my_dict[('key2', 'key1')]会给我一个KeyError

1D元组词典的一个解决方法是创建一个try / except。

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

这似乎不直观,感觉更像是对这个问题的“创可贴”。

我还没有测试过的一种方法是一维字典,其中密钥使用一个自定义类的实例,该类将key1key2作为属性。为了做到这一点,对象必须是不可变的和可散列的,其中散列函数将使用对象的属性并产生相同的“散列键”而不管属性的顺序如何。我以前从未这样做过,也不知道该怎么做。这是正确的方法吗?我觉得非常愚蠢,我无法弄清楚这一点,因为似乎有一个简单的答案。

3 个答案:

答案 0 :(得分:2)

如果您希望无论顺序如何都能比较相等的键,您可以使用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'

您可以在冻结集中包含尽可能多的值,它们仍然可以比较相等,使用冻结集对查找也很有效:

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所以对于任何超过一对检查每个可能的排列是不现实或远程有效。

答案 1 :(得分:1)

您可以按照建议使用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

答案 2 :(得分:0)

怎么样

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'