【问题标题】:How to retrieve from python dict where key is only partially known?如何从仅部分知道密钥的python dict中检索?
【发布时间】:2011-10-26 07:53:18
【问题描述】:

我有一个dict,它具有我不知道其确切值的字符串类型键(因为它们是在其他地方动态生成的)。但是,我知道我想要的键包含一个特定的子字符串,并且带有该子字符串的单个键肯定在字典中。

检索此键的值的最佳或“最 Pythonic”的方法是什么?

我想到了两种策略,但都让我感到厌烦:

for k,v in some_dict.items():
    if 'substring' in k:
        value = v
        break

-- 或者--

value = [v for (k,v) in some_dict.items() if 'substring' in k][0]

第一种方法体积庞大且有些丑陋,而第二种方法更简洁,但是索引到列表理解的额外步骤([0])让我很恼火。有没有更好的方式来表达第二个版本,或者更简洁的方式来编写第一个版本?

【问题讨论】:

  • 与大多数其他语言相比,它们看起来都非常简洁。但我认为第一个更具可读性。
  • 为什么知道子串?你有更多的信息吗?您可以对字典进行某种解析/转换吗?你会在同一个字典上做很多类似的查找吗?
  • 你应该试试k.startswith('substring')k.endswith('substring'),如果它在开头或结尾;它们可能会更快。
  • 如果查找所有您拥有的 some_dict 的内容,那么它完全没用,列表会更好。如果您有要匹配的子字符串列表,则时间复杂度为 O(N**2)。您需要对键进行索引才能有效地执行此操作,但像 Sphinx 这样的全文搜索引擎基本上可以做到这一点。
  • first method is bulky and somewhat ugly, while the second is cleaner 这里的小评论:第二个不干净,它只是少了\n 字符。有一些奇怪的信念认为单行代码工作得更快并且更具可读性。他们不是。

标签: python dictionary key substring


【解决方案1】:

我更喜欢第一个版本,尽管我会使用some_dict.iteritems()(如果您使用的是 Python 2),因为这样您就不必事先构建所有项目的完整列表。相反,您遍历 dict 并在完成后立即中断。

在 Python 3 上,some_dict.items(2) 已经生成字典视图,因此这已经是一个合适的迭代器。

【讨论】:

    【解决方案2】:

    可以选择使用第一个版本的性能属性编写第二个版本。

    使用 generator expression 代替列表解析:

    value = next(v for (k,v) in some_dict.iteritems() if 'substring' in k)
    

    括号内的表达式将返回一个迭代器,然后您将要求提供下一个,即第一个元素。不处理其他元素。

    【讨论】:

    • 绝对在 Python 2 上使用iteritems;否则这是我的首选方式。
    • @agf:感谢您发现故障。实际上,使用items() 毫无意义。
    • 这是一个巧妙的技巧。我很想在某处写first = next 以使其阅读更清楚......虽然first = next 行本身就是一种WTF 我想o_O
    • @Karl 让我笑出声来。您是否有一个单独的帐户在您不在家时使用?为什么?
    • @agf 我偏执于使用我需要登录的任何东西,而不是在我自己的计算机上。所以这只是一个临时ID,甚至没有注册。可能不值得担心,但你永远不知道……我也不完全确定我使用的是什么密码,我认为这可能是一个随机生成的密码……当我回家时应该检查我的笔记>_
    【解决方案3】:

    这个怎么样:

    value = (v for (k,v) in some_dict.iteritems() if 'substring' in k).next()
    

    当它找到第一个匹配时会立即停止。

    但它仍然具有 O(n) 复杂度,其中 n 是键值对的数量。您需要后缀列表或后缀树之类的东西来加快搜索速度。

    【讨论】:

    • 这是我考虑过的,但我个人觉得它比我提出的列表理解“更难看”。西蒙的回答解决了丑陋的问题,但你说得对,这个解决方案比我提出的解决方案要好,至少在性能方面。
    • 免费的next 函数也可用于指定在迭代器上的.next() 调用引发StopIteration 时返回的默认值。
    【解决方案4】:
    class MyDict(dict):
        def __init__(self, *kwargs):
            dict.__init__(self, *kwargs)
    
        def __getitem__(self,x):
            return next(v for (k,v) in self.iteritems() if x in k)
    
    
    
    # Defining several dicos ----------------------------------------------------    
    some_dict = {'abc4589':4578,'abc7812':798,'kjuy45763':1002}
    
    another_dict = {'boumboum14':'WSZE x478',
                    'tagada4783':'ocean11',
                    'maracuna102455':None}
    
    still_another = {12:'jfg',45:'klsjgf'}
    
    
    
    # Selecting the dicos whose __getitem__ method will be changed -------------       
    name,obj = None,None
    selected_dicos = [ (name,obj) for (name,obj) in globals().iteritems()
                       if type(obj)==dict
                       and all(type(x)==str for x in obj.iterkeys())]
    
    print 'names of selected_dicos ==',[ name for (name,obj) in selected_dicos] 
    
    
    
    # Transforming the selected dicos in instances of class MyDict -----------
    for k,v in selected_dicos:
        globals()[k] = MyDict(v)
    
    
    
    # Exemple of getting a value ---------------------------------------------      
    print "some_dict['7812'] ==",some_dict['7812']
    

    结果

    names of selected_dicos == ['another_dict', 'some_dict']
    some_dict['7812'] == 798
    

    【讨论】:

    • 在我的回答中,有什么不满意的?所以不应该允许在没有评论的情况下投反对票
    • @Jakub Mikians 谢谢,但我更想了解我的回答受到了哪些指责
    • 我认为这是一个非常有趣的想法,但你有点过于冗长了。我相当确定为 MyDict 定义 __init__ 函数是多余的,因为如果 MyDict 没有自己的函数,它将使用 dict 的 __init__。由于您的解决方案使用了与 Simon 相同的基本答案,因此我认为对于任何想要解决此类问题的更复杂解决方案的人来说,它都有好处。
    • 这个答案基本上采用了简单的方法,并且有点可疑地将它包装在一个类中,然后显示了一个类的一堆样板用法,其用法应该是不言自明的,使用通常被认为相当深的魔术以便在适当的位置转换字典。哦,还有一个列表理解,然后直接馈送到一个 for 循环,直接循环会更简单,并且决定包装哪些 dicts 是基于显式类型检查(一般不赞成)。简而言之,这非常不符合 Python 风格。
    【解决方案5】:

    如果有很多键,但字符串很容易从子字符串重构,那么它可以更快地重构它。例如通常您知道密钥的开始,但不知道附加的日期戳。 (例如,您可能只需要尝试 365 个日期,而不是遍历数百万个键)。 不太可能是这种情况,但我想我还是会建议的。 例如

    >>> names={'bob_k':32,'james_r':443,'sarah_p':12}
    >>> firstname='james' #you know the substring james because you have a list of firstnames
    >>> for c in "abcdefghijklmnopqrstuvwxyz":
    ...     name="%s_%s"%(firstname,c)
    ...     if name in names:
    ...             print name
    ... 
    james_r
    

    【讨论】:

    • 谢天谢地,我没有这么大的 dict 以至于这种策略是必要的,但它我没有想到的一个新颖的想法。 +1
    猜你喜欢
    • 1970-01-01
    • 2017-12-21
    • 1970-01-01
    • 2012-02-27
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    相关资源
    最近更新 更多