【问题标题】:2D Dictionary or other data structure where order of keys doesn't matter二维字典或其他数据结构,其中键的顺序无关紧要
【发布时间】:2023-03-05 11:04:01
【问题描述】:

我想创建一个数据结构,它可以接收一对键(字符串)并返回一个值(也可以是字符串)。无论输入 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

一维元组字典的一种解决方法是创建一个 try/except。

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

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

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

【问题讨论】:

    标签: python dictionary data-structures


    【解决方案1】:

    如果您希望键比较相等而不考虑顺序,您可以使用 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 中拥有任意数量的值,但它们仍然比较相等,使用frozen set 对查找也很有效:

    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
    

    即使对 freezeet 创建和查找两个元素的时间安排也更快:

    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 个!要检查的 perms,对于 6,您有 720,因此对于超过一对检查每个可能的排列的任何东西都是不现实的或远程有效的。

    【讨论】:

    • 这似乎是迄今为止我找到的最佳解决方案。感谢您向我展示frozenset
    • @koreebay,frozensets 是不可变的,因此它们可以用作 dict 键,如果你有 1 或 101 个不同的字符串,它们将工作
    • 假设这对键总是以字符串的形式出现。如果每次我想查找字典时都必须从两个字符串中创建一个新的 frozenset 键,这仍然可行吗?
    • @koreebay,是的,它会正常工作,如果你要增加里面的字符串数量,它也会大大优于尝试每个排列,即使有 3 个键你有 6 个 perms,即 3 !每次都要考虑,对于 6 个字符串,您需要考虑 720 种排列,这样您就可以看到它在比较时的扩展程度。
    【解决方案2】:

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

    【讨论】:

    • 如果我决定需要更多的键(例如,从 6 个键中获取值,顺序无关紧要),这会是首选方法吗?
    • 当然。您甚至可以实现任意N 元素。尽管在那种情况下,我并没有真正看到建议的frozenset 解决方案有什么好处,所以自定义实现确实没有任何意义。我经常做的是创建一个类型别名,比如Key = frozenset。如果您以后需要对类进行更改,则可以,更不用说您可以为对象赋予更有意义的名称。您可以像往常一样实例化,例如 k = Key()
    • 明白了。所以我首先需要根据我的数据创建新字典,这需要我为键对创建一个frozenset。然后,当我想在字典中查找某些内容时,我必须使用我的输入键创建一个新的 frozenset 并使用它来获取我的值。
    【解决方案3】:

    怎么样

    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'
    

    【讨论】:

    • 如果你有包含几个元素的元组,你将不得不手动检查每个组合
    • @PadraicCunningham 不,这将返回my_dict[(key2, key1)],或者,如果它不存在,则返回my_dict[(key1, key2)]
    • @Delgan,是的,我错过了括号,对于更多的两个元素仍然不太实用
    • @PadraicCunningham 在这种情况下,元组只有 2 个值是固定的。我假设提问者本质上是用键而不是索引制作一个二维矩阵。
    • @YashMehrotra,你有 n!排序键的方法,所以即使是 3 也不是你真正想要考虑的事情,使用 if/else 进行检查也比 get 更快
    【解决方案4】:

    这是我发现的。列表的维度必须相同。

    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
    

    Frozenset 可能更好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-07
      • 1970-01-01
      • 1970-01-01
      • 2019-03-06
      • 2021-10-24
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      相关资源
      最近更新 更多