【问题标题】:Finding a key recursively in a dictionary在字典中递归查找键
【发布时间】:2013-02-04 10:18:01
【问题描述】:

我正在尝试编写一个非常简单的函数来递归搜索可能嵌套的(在最极端的情况下十层深)Python 字典并返回它从给定键中找到的第一个值。

我不明白为什么我的代码不适用于嵌套字典。

def _finditem(obj, key):
    if key in obj: return obj[key]
    for k, v in obj.items():
        if isinstance(v,dict):
            _finditem(v, key)

print _finditem({"B":{"A":2}},"A")

它返回None

但是,对于_finditem({"B":1,"A":2},"A"),它确实有效,返回2

我确定这是一个简单的错误,但我找不到它。我觉得标准库或collections 中可能已经有相关内容,但我也找不到。

【问题讨论】:

  • 请注意,检查它是否是 dict 对象是个坏主意,因为它排除了类似 dict 的对象。相反,请执行 try: ... except TypeError: ...。 (请求宽恕,而不是许可)。
  • 还要注意,由于字典本质上是无序的,如果你的嵌套结构中有多个键“A”,你永远不知道你会得到哪一个(就像一盒巧克力我想。 ..)
  • @mgilson 在这个特定的情况下,没关系,我考虑过。 :)
  • @frb -- 我认为它可能没问题,我只是想确保它被记录在某个地方:)。

标签: python search recursion dictionary


【解决方案1】:

递归的时候需要return_finditem的结果

def _finditem(obj, key):
    if key in obj: return obj[key]
    for k, v in obj.items():
        if isinstance(v,dict):
            return _finditem(v, key)  #added return statement

要修复实际算法,您需要意识到 _finditem 如果没有找到任何东西,则返回 None,因此您需要明确检查以防止提前返回:

def _finditem(obj, key):
    if key in obj: return obj[key]
    for k, v in obj.items():
        if isinstance(v,dict):
            item = _finditem(v, key)
            if item is not None:
                return item

当然,如果您的任何字典中有 None 值,这将失败。在这种情况下,你可以为这个函数设置一个标记object(),并在你没有找到任何东西的情况下返回它——然后你可以检查sentinel以了解你是否找到了一些东西。

【讨论】:

  • 这似乎是编写递归函数时最常见的错误。
  • @DanielRoseman -- 耸耸肩 -- 我自己也犯过几次这个错误。但是,当您的函数返回 None 而您不知道为什么时,这是一个提示;-)
  • 谢谢,这应该是显而易见的。我看了这个好一个小时!
  • @frb -- 没问题。这样的事情发生在每个人身上。
  • @mgilson 实际上,我刚刚意识到,当需要的键在字典中是第二个时,这不起作用。有什么线索吗?这是因为它在 obj.items() 中的第一个键值之后返回。
【解决方案2】:

这是一个搜索包含嵌套字典和列表的字典的函数。它会创建一个结果值列表。

def get_recursively(search_dict, field):
    """
    Takes a dict with nested lists and dicts,
    and searches all dicts for a key of the field
    provided.
    """
    fields_found = []

    for key, value in search_dict.iteritems():

        if key == field:
            fields_found.append(value)

        elif isinstance(value, dict):
            results = get_recursively(value, field)
            for result in results:
                fields_found.append(result)

        elif isinstance(value, list):
            for item in value:
                if isinstance(item, dict):
                    more_results = get_recursively(item, field)
                    for another_result in more_results:
                        fields_found.append(another_result)

    return fields_found

【讨论】:

【解决方案3】:

这是一种使用“堆栈”和"stack of iterators" pattern(感谢 Gareth Rees)的方法:

def search(d, key, default=None):
    """Return a value corresponding to the specified key in the (possibly
    nested) dictionary d. If there is no item with that key, return
    default.
    """
    stack = [iter(d.items())]
    while stack:
        for k, v in stack[-1]:
            if isinstance(v, dict):
                stack.append(iter(v.items()))
                break
            elif k == key:
                return v
        else:
            stack.pop()
    return default

print(search({"B": {"A": 2}}, "A")) 将打印 2

【讨论】:

    【解决方案4】:

    只是想让它更短:

    def get_recursively(search_dict, field):
        if isinstance(search_dict, dict):
            if field in search_dict:
                return search_dict[field]
            for key in search_dict:
                item = get_recursively(search_dict[key], field)
                if item is not None:
                    return item
        elif isinstance(search_dict, list):
            for element in search_dict:
                item = get_recursively(element, field)
                if item is not None:
                    return item
        return None
    

    【讨论】:

      【解决方案5】:

      由于缺乏声誉,我无法对@mgilston 提出的已接受解决方案添加评论。如果要搜索的键在列表中,则该解决方案不起作用。

      遍历列表的元素并调用递归函数应该扩展功能以在嵌套列表中查找元素:

      def _finditem(obj, key):
          if key in obj: return obj[key]
          for k, v in obj.items():
              if isinstance(v,dict):
                  item = _finditem(v, key)
                  if item is not None:
                      return item
              elif isinstance(v,list):
                  for list_item in v:
                      item = _finditem(list_item, key)
                      if item is not None:
                          return item
      
      print(_finditem({"C": {"B": [{"A":2}]}}, "A"))
      
      

      【讨论】:

        【解决方案6】:

        我必须创建一个通用版本,它在包含多个嵌套字典和列表的字典中找到唯一指定的键(指定所需值的路径的最小字典)。

        对于下面的示例,创建了一个目标字典进行搜索,并使用通配符“???”创建了键。运行时返回值“D”

        def lfind(query_list:List, target_list:List, targ_str:str = "???"):
            for tval in target_list:
                #print("lfind: tval = {}, query_list[0] = {}".format(tval, query_list[0]))
                if isinstance(tval, dict):
                    val = dfind(query_list[0], tval, targ_str)
                    if val:
                        return val
                elif tval == query_list[0]:
                    return tval
        
        def dfind(query_dict:Dict, target_dict:Dict, targ_str:str = "???"):
            for key, qval in query_dict.items():
                tval = target_dict[key]
                #print("dfind: key = {}, qval = {}, tval = {}".format(key, qval, tval))
                if isinstance(qval, dict):
                    val =  dfind(qval, tval, targ_str)
                    if val:
                        return val
                elif isinstance(qval, list):
                    return lfind(qval, tval, targ_str)
                else:
                    if qval == targ_str:
                        return tval
                    if qval != tval:
                        break
        
        def find(target_dict:Dict, query_dict:Dict):
            result = dfind(query_dict, target_dict)
            return result
        
        
        
        target_dict = {"A":[
            {"key1":"A", "key2":{"key3": "B"}},
            {"key1":"C", "key2":{"key3": "D"}}]
        }
        query_dict = {"A":[{"key1":"C", "key2":{"key3": "???"}}]}
        
        result = find(target_dict, query_dict)
        print("result = {}".format(result))
        

        【讨论】:

          【解决方案7】:

          我想我会把我的帽子扔在戒指上,这将允许对任何实现 __getitem__ 方法的递归请求。

          def _get_recursive(obj, args, default=None):
              """Apply successive requests to an obj that implements __getitem__ and
              return result if something is found, else return default"""
              if not args:
                  return obj
              try:
                  key, *args = args
                  _obj = object.__getitem__(obj, key)
                  return _get_recursive(_obj, args, default=default)
              except (KeyError, IndexError, AttributeError):
                  return default
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-12-16
            • 2014-01-08
            • 1970-01-01
            • 2022-01-17
            • 2018-08-15
            • 1970-01-01
            • 2017-12-02
            相关资源
            最近更新 更多