在嵌套字典和列表中查找某个键的所有出现位置

2024-12-30 08:41:00
admin
原创
46
摘要:问题描述:我有一本这样的字典:{ "id": "abcde", "key1": "blah", "key2": "blah blah", "nest...

问题描述:

我有一本这样的字典:

{
    "id": "abcde",
    "key1": "blah",
    "key2": "blah blah",
    "nestedlist": [
        {
            "id": "qwerty",
            "nestednestedlist": [
                {
                    "id": "xyz",
                    "keyA": "blah blah blah"
                },
                {
                    "id": "fghi",
                    "keyZ": "blah blah blah"
                }
            ],
            "anothernestednestedlist": [
                {
                    "id": "asdf",
                    "keyQ": "blah blah"
                },
                {
                    "id": "yuiop",
                    "keyW": "blah"
                }
            ]
        }
    ]
}

基本上是一个具有任意深度的嵌套列表、字典和字符串的字典。

遍历此内容以提取每个“id”键的值的最佳方法是什么?我想实现与“//id”等 XPath 查询相同的功能。“id”的值始终是一个字符串。

所以从我的例子来看,我需要的输出基本上是:

["abcde", "qwerty", "xyz", "fghi", "asdf", "yuiop"]

顺序并不重要。


解决方案 1:

我发现这个问答非常有趣,因为它为同一个问题提供了几种不同的解决方案。我把所有这些函数都拿来,用一个复杂的字典对象对它们进行了测试。我不得不从测试中剔除两个函数,因为它们有太多失败的结果,而且它们不支持将列表或字典作为值返回,而我认为这很重要,因为一个函数应该为几乎所有即将到来的数据做好准备。

因此我通过该模块对其他函数进行了 100,000 次迭代timeit,输出结果如下:

0.11 usec/pass on gen_dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6.03 usec/pass on find_all_items(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.15 usec/pass on findkeys(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1.79 usec/pass on get_recursively(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.14 usec/pass on find(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
0.36 usec/pass on dict_extract(k,o)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

所有函数都有相同的搜索针(“logging”)和相同的字典对象,其构造如下:

o = { 'temparature': '50', 
      'logging': {
        'handlers': {
          'console': {
            'formatter': 'simple', 
            'class': 'logging.StreamHandler', 
            'stream': 'ext://sys.stdout', 
            'level': 'DEBUG'
          }
        },
        'loggers': {
          'simpleExample': {
            'handlers': ['console'], 
            'propagate': 'no', 
            'level': 'INFO'
          },
         'root': {
           'handlers': ['console'], 
           'level': 'DEBUG'
         }
       }, 
       'version': '1', 
       'formatters': {
         'simple': {
           'datefmt': "'%Y-%m-%d %H:%M:%S'", 
           'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
         }
       }
     }, 
     'treatment': {'second': 5, 'last': 4, 'first': 4},   
     'treatment_plan': [[4, 5, 4], [4, 5, 4], [5, 5, 5]]
}

所有函数都提供相同的结果,但时间差异很大!该函数gen_dict_extract(k,o)是我从此处的函数改编而来的,实际上它非常类似于findAlfe 的函数,主要区别在于,我正在检查给定的对象是否具有 iteritems 函数,以防在递归期间传递字符串:

# python 2
def gen_dict_extract(key, var):
    if hasattr(var,'iteritems'): # hasattr(var,'items') for python 3
        for k, v in var.iteritems(): # var.items() for python 3
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in gen_dict_extract(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in gen_dict_extract(key, d):
                        yield result

因此,此变体是此处最快且最安全的函数。 和find_all_items非常慢,与第二慢的函数相差甚远get_recursivley,而除 之外的其余函数dict_extract彼此接近。fun和函数keyHole仅在您查找字符串时才有效。

这里有有趣的学习方面:)

解决方案 2:

d = { "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [ 
    { "id" : "qwerty",
        "nestednestedlist" : [ 
        { "id" : "xyz", "keyA" : "blah blah blah" },
        { "id" : "fghi", "keyZ" : "blah blah blah" }],
        "anothernestednestedlist" : [ 
        { "id" : "asdf", "keyQ" : "blah blah" },
        { "id" : "yuiop", "keyW" : "blah" }] } ] } 

def find(field_name, d, current_path=''):
    if not isinstance(d, dict):
        return

    if field_name in d:
        yield current_path

    for k in d:
        if isinstance(d[k], list):
            index = 0
            for array_element in d[k]:
                for j in find(field_name, array_element, current_path + f'.{k}.[{index}]'):
                    yield j

                index += 1

        elif isinstance(d[k], dict):
            for found in find(field_name, d[k], current_path + f'.{k}'):
                yield found

>>> list(find('id', d))
['', '.nestedlist.[0]', '.nestedlist.[0].nestednestedlist.[0]', '.nestedlist.[0].nestednestedlist.[1]', '.nestedlist.[0].anothernestednestedlist.[0]', '.nestedlist.[0].anothernestednestedlist.[1]']

解决方案 3:

d = { "id" : "abcde",
    "key1" : "blah",
    "key2" : "blah blah",
    "nestedlist" : [
    { "id" : "qwerty",
        "nestednestedlist" : [
        { "id" : "xyz", "keyA" : "blah blah blah" },
        { "id" : "fghi", "keyZ" : "blah blah blah" }],
        "anothernestednestedlist" : [
        { "id" : "asdf", "keyQ" : "blah blah" },
        { "id" : "yuiop", "keyW" : "blah" }] } ] }


def findkeys(node, kv):
    if isinstance(node, list):
        for i in node:
            for x in findkeys(i, kv):
               yield x
    elif isinstance(node, dict):
        if kv in node:
            yield node[kv]
        for j in node.values():
            for x in findkeys(j, kv):
                yield x

print(list(findkeys(d, 'id')))

解决方案 4:

def find(key, value):
  for k, v in value.items():
    if k == key:
      yield v
    elif isinstance(v, dict):
      for result in find(key, v):
        yield result
    elif isinstance(v, list):
      for d in v:
        for result in find(key, d):
          yield result

编辑:@Anthon 注意到这不适用于直接嵌套列表。如果您的输入中包含此内容,则可以使用以下内容:

def find(key, value):
  for k, v in (value.items() if isinstance(value, dict) else
               enumerate(value) if isinstance(value, list) else []):
    if k == key:
      yield v
    elif isinstance(v, (dict, list)):
      for result in find(key, v):
        yield result

但我认为原始版本更容易理解,所以我将保留它。

解决方案 5:

pip install nested-lookup
正是您想要的:

document = [ { 'taco' : 42 } , { 'salsa' : [ { 'burrito' : { 'taco' : 69 } } ] } ]

>>> print(nested_lookup('taco', document))
[42, 69]

解决方案 6:

我只是想通过使用yield from和接受顶级列表来迭代@hexerei-software 的优秀答案。

def gen_dict_extract(var, key):
    if isinstance(var, dict):
        for k, v in var.items():
            if k == key:
                yield v
            if isinstance(v, (dict, list)):
                yield from gen_dict_extract(v, key)
    elif isinstance(var, list):
        for d in var:
            yield from gen_dict_extract(d, key)

解决方案 7:

另一种变体,其中包括找到的结果的嵌套路径(注意:此版本不考虑列表):

def find_all_items(obj, key, keys=None):
    """
    Example of use:
    d = {'a': 1, 'b': 2, 'c': {'a': 3, 'd': 4, 'e': {'a': 9, 'b': 3}, 'j': {'c': 4}}}
    for k, v in find_all_items(d, 'a'):
        print "* {} = {} *".format('->'.join(k), v)    
    """
    ret = []
    if not keys:
        keys = []
    if key in obj:
        out_keys = keys + [key]
        ret.append((out_keys, obj[key]))
    for k, v in obj.items():
        if isinstance(v, dict):
            found_items = find_all_items(v, key, keys=(keys+[k]))
            ret += found_items
    return ret

解决方案 8:

此函数以递归方式搜索包含嵌套字典和列表的字典。它构建一个名为 fields_found 的列表,其中包含每次找到字段时的值。'field' 是我在字典及其嵌套列表和字典中寻找的键。

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

解决方案 9:

glom是一个很棒的搜索和重构库,还可以使用 glob 进行嵌套查找。示例:

In [1]: import glom

In [2]: data = { "id" : "abcde", "key1" : "blah", ... }  # OP example

In [3]: glom.glom(data, '**.id')
Out[3]: ['abcde', 'qwerty', 'xyz', 'fghi', 'asdf', 'yuiop']

嵌套级别用点分隔(将它们视为 Unix 全局变量中的斜线),单星号表示一层的占位符,双星号表示多层的占位符。在上面的例子中,**.id表示“id任何层上的键”。更多示例:

In [4]: glom.glom(d, ('*.*.id', glom.Flatten()))
Out[4]: ['qwerty']

这将仅遍历第三级的所有键并提取的值id,而空结果将被丢弃(嵌套结果列表被展平)。

id另一个仅收集列表中特定值的示例anothernestednestedlist

In [5]: glom.glom(d, ('nestedlist', [('anothernestednestedlist', ['id'])]))
Out[5]: [['asdf', 'yuiop']]

但是,该库的功能远不止这些。请阅读glom文档了解更多功能。

解决方案 10:

我无法让这里发布的解决方案立即可用,所以我想写一些更灵活的东西。

下面的递归函数应该允许您在任意深度嵌套的字典和列表中收集满足给定键的某些正则表达式模式的所有值。

import re

def search(dictionary, search_pattern, output=None):
    """
    Search nested dictionaries and lists using a regex search
    pattern to match a key and return the corresponding value(s).
    """
    if output is None:
        output = []

    pattern = re.compile(search_pattern)
    
    for k, v in dictionary.items():
        pattern_found = pattern.search(k)
        if not pattern_found:
            if isinstance(v, list):
                for item in v:
                    if isinstance(item, dict):
                        search(item, search_pattern, output)
            if isinstance(v, dict):
                search(v, search_pattern, output)
        else:
            if pattern_found:
                output.append(v)

    return output

如果您想搜索特定术语,您可以始终将搜索模式设置为类似r'some_term'

解决方案 11:

以下是我对此的尝试:

def keyHole(k2b,o):
  # print "Checking for %s in "%k2b,o
  if isinstance(o, dict):
    for k, v in o.iteritems():
      if k == k2b and not hasattr(v, '__iter__'): yield v
      else:
        for r in  keyHole(k2b,v): yield r
  elif hasattr(o, '__iter__'):
    for r in [ keyHole(k2b,i) for i in o ]:
      for r2 in r: yield r2
  return

前任。:

>>> findMe = {'Me':{'a':2,'Me':'bop'},'z':{'Me':4}}
>>> keyHole('Me',findMe)
<generator object keyHole at 0x105eccb90>
>>> [ x for x in keyHole('Me',findMe) ]
['bop', 4]

解决方案 12:

跟进@hexerei 软件的回答和@bruno-bronosky 的评论,如果您想遍历键列表/集合:

def gen_dict_extract(var, keys):
   for key in keys:
      if hasattr(var, 'items'):
         for k, v in var.items():
            if k == key:
               yield v
            if isinstance(v, dict):
               for result in gen_dict_extract([key], v):
                  yield result
            elif isinstance(v, list):
               for d in v:
                  for result in gen_dict_extract([key], d):
                     yield result    

请注意,我传递的是一个包含单个元素 ([key]} 的列表,而不是字符串键。

解决方案 13:

由于Python 中存在最大递归深度,因此我会考虑实现任意大小的迭代方法:

def get_ids(data: Dict, key: str) -> List:
    stack = [data]
    result = []
    while stack:
        elem = stack.pop()
        if isinstance(elem, dict):
            for k, v in elem.items():
                if k == key:
                    result.append(v)
                if isinstance(elem, (list, dict)):
                    stack.append(v)
        elif isinstance(elem, list):
            for obj in elem:
                stack.append(obj)
    return result

解决方案 14:

简介

得益于 Python 十年的演进(致力于 3.11),在对投票最多的解决方案发表评论后,我开发了一个变体,事后我意识到它遵循了许多提议解决方案的步骤。不过,我的目标是从深度和广度两方面为响应添加背景信息。

XPath 解决方案

我的解决方案不仅为找到的节点提供了一种 XPath,还根据需要提供了周围的上下文。为此,它接受一个可迭代的键,与目标键一起输出。显然,它提供了key: value字典元素,以提高可读性:

def extract(var, key, context_keys=(), xpath=''):
    if isinstance(var, dict):
        if key in var:
            yield {f'{xpath}.{key}': var[key]} | {f'{xpath}.{key}': value for key, value in var.items() if key in context_keys}
        for subkey, value in var.items():
            yield from extract(value, key, context_keys, f'{xpath}.{subkey}')
    elif isinstance(var, list):
        for i, elem in enumerate(var):
            yield from extract(elem, key, context_keys, f'{xpath}[{i}]')

通过此查找'id'可以检索到:

[
  {
    ".id": "abcde"
  },
  {
    ".nestedlist[0].id": "qwerty"
  },
  {
    ".nestedlist[0].nestednestedlist[0].id": "xyz"
  },
  {
    ".nestedlist[0].nestednestedlist[1].id": "fghi"
  },
  {
    ".nestedlist[0].anothernestednestedlist[0].id": "asdf"
  },
  {
    ".nestedlist[0].anothernestednestedlist[1].id": "yuiop"
  }
]

显然,由于所有键都代表不同的 XPath,我们可以将条目统一到一个字典中,使用reduce或理解,得到如下结果:

{
  ".id": "abcde",
  ".nestedlist[0].id": "qwerty",
  ".nestedlist[0].nestednestedlist[0].id": "xyz",
  ".nestedlist[0].nestednestedlist[1].id": "fghi",
  ".nestedlist[0].anothernestednestedlist[0].id": "asdf",
  ".nestedlist[0].anothernestednestedlist[1].id": "yuiop"
}

上下文可以用于这样的情况:

>>> pp(reduce(lambda acc, elem: acc | elem, extract(d, 'id', 'keyA')))
{
  ".id": "abcde",
  ".nestedlist[0].id": "qwerty",
  ".nestedlist[0].nestednestedlist[0].id": "xyz",
  ".nestedlist[0].nestednestedlist[0].keyA": "blah blah blah", # <-- HERE
  ".nestedlist[0].nestednestedlist[1].id": "fghi",
  ".nestedlist[0].anothernestednestedlist[0].id": "asdf",
  ".nestedlist[0].anothernestednestedlist[1].id": "yuiop"
}

我正在研究一种解决方案,返回一个具有与原始结构相同的对象,但只包含所需的键。如果我让它工作,我会在这里添加它。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1006  
  华为作为全球领先的科技公司,其成功的关键之一在于其高效的研发管理体系——集成产品开发(IPD)。IPD不仅仅是一套流程工具,更是一种团队协作与文化融合的实践体系。通过IPD,华为将跨部门的资源、能力和智慧有效整合,实现了从产品概念到市场交付的全流程高效运作。IPD的核心在于打破传统职能部门的壁垒,强调团队协作与文化的深...
IPD集成产品开发流程   0  
  创新是企业持续发展的核心驱动力,而集成产品开发(IPD, Integrated Product Development)作为一种先进的管理方法,能够帮助企业实现从产品概念到市场落地的全流程优化。通过系统化的流程设计和跨职能团队的协同合作,IPD不仅能够提升开发效率,还能显著降低风险,确保产品在市场上具备竞争力。许多企业...
华为IPD流程   0  
  在快速变化的市场中,企业面临着缩短产品上市时间的巨大压力。消费者需求的多样化和技术的迅猛发展,使得产品生命周期日益缩短。企业必须更快地推出新产品,以抢占市场先机并满足客户期望。然而,传统的产品开发流程往往效率低下,难以应对这种挑战。集成产品开发(IPD)流程作为一种系统化的产品开发方法,能够帮助企业在确保质量的同时,显...
IPD培训课程   0  
  研发IPD(Integrated Product Development,集成产品开发)流程是企业实现高效产品开发的核心方法论之一。它通过跨部门协作、并行工程和结构化流程,确保产品从概念到市场的高效交付。然而,在IPD流程中,绩效评估与改进方法的设计与实施往往成为企业面临的重大挑战。由于研发活动的复杂性和不确定性,传统...
IPD流程分为几个阶段   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用