在嵌套字典中搜索值 Python

19 投票
5 回答
40589 浏览
提问于 2025-04-17 20:43

在这个例子中,我们要在一个字典里查找某个值,并找出它的父字典名称(也就是键)。

Dictionary = {dict1:{
        'part1': {
            '.wbxml': 'application/vnd.wap.wbxml',
            '.rl': 'application/resource-lists+xml',    
        },
        'part2':
            {'.wsdl': 'application/wsdl+xml',
            '.rs': 'application/rls-services+xml',
            '.xop': 'application/xop+xml',
            '.svg': 'image/svg+xml',
            },
        'part3':{...}, ...

   dict2:{
          'part1': {    '.dotx': 'application/vnd.openxmlformats-..'                           
            '.zaz': 'application/vnd.zzazz.deck+xml',
            '.xer': 'application/patch-ops-error+xml',}  
          },
          'part2':{...},
          'part3':{...},...  

    },...

在上面的字典中,我需要查找的值是:"image/svg+xml"。这里面没有任何值是重复的。怎么才能找到"image/svg+xml",并返回它的父键,比如字典{ dict1:"part2" }

请注意:解决方案应该在不修改的情况下,适用于Python 2.7Python 3.3这两个版本。

5 个回答

1

这个代码的作用是遍历一个嵌套的字典,寻找一个特定的值。当找到这个值时,它会打印出到这个值的完整路径。代码里保留了所有的注释和打印语句,目的是为了教学(这段代码不适合直接用在生产环境中!)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Jan 24 17:16:46 2022

@author: wellington
"""


class Tree(dict):
    """
    allows autovivification as in Perl hashes
    """

    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

# tracking the key sequence when seeking the target
key_list = Tree()

# dict storing the target success result
success = Tree()


# example nested dict of dicts and lists
E = {
    'AA':
        {
          'BB':
               {'CC':
                     {
                      'DD':
                          {
                           'ZZ':'YY',
                           'WW':'PP'
                           },
                       'QQ':
                           {
                            'RR':'SS'
                            },
                     },
                'II': 
                     {
                      'JJ':'KK'
                     }, 
                'LL':['MM', 'GG', 'TT']
               }
        }
    }


def find_keys_from_value(data, target):
    """
    recursive function -
    given a value it returns all the keys in the path to that value within
    the dict "data"
    there are many paths and many false routes
    at the end of a given path if success has not been achieved
    the function discards keys to get back to the next possible path junction
    """

    print(f"the number of keys in the local dict is {len(data)}")
    key_counter = 0

    for key in data:
        key_counter += 1

        # if target has been located stop iterating through keys
        if success[target] == 1:
            break
        else:
            # eliminate prior key from path that did not lead to success
            if key_counter > 1:
                k_list.pop()
            # add key to new path
            k_list.append(key)
            print(f"printing k_list after append{k_list}")

        # if target located set success[target] = 1 and exit
        if key == target or data[key] == target:
            key_list[target] = k_list
            success[target] = 1
            break
        # if the target has not been located check to see if the value
        # associated with the new key is a dict and if so return to the
        # recursive function with the new dict as "data"
        elif isinstance(data[key], dict):
            print(f"\nvalue is dict\n {data[key]}")
            find_keys_from_value(data[key], target)

        # check to see if the value associated with the new key is a list
        elif isinstance(data[key], list):
            # print("\nv is list\n")
            # search through the list
            for i in data[key]:

                # check to see if the list element is a dict
                # and if so return to the recursive function with
                # the new dict as "data
                if isinstance(i, dict):
                    find_keys_from_value(i, target)

                # check to see if each list element is the target
                elif i == target:
                    print(f"list entry {i} is target")
                    success[target] = 1
                    key_list[target] = k_list
                elif i != target:
                    print(f"list entry {i} is not target")
                    print(f"printing k_list before pop_b {k_list}")
                    print(f"popping off key_b {key}")

        # so if value is not a key and not a list and not the target then
        # discard the key from the key list
        elif data[key] != target:
            print(f"value {data[key]} is not target")
            print(f"printing k_list before removing key_before {k_list}")
            print(f"removing key_c {key}")
            k_list.remove(key)


# select target values
values = ["PP", "SS", "KK", "TT"]
success = {}

for target in values:
    print(f"\nlooking for target {target}")
    success[target] = 0
    k_list = []
    find_keys_from_value(E, target)
    print(f"\nprinting key_list for target {target}")
    print(f"{key_list[target]}\n")
    print("\n****************\n\n")
1

这里有两种类似的简单粗暴的方法来完成这种操作。函数 find_parent_dict1 使用了列表推导式,但如果你对这个不太熟悉的话,find_parent_dict2 则使用了臭名昭著的嵌套 for 循环。

Dictionary = {'dict1':{'part1':{'.wbxml':'1','.rl':'2'},'part2':{'.wbdl':'3','.rs':'4'}},'dict2':{'part3':{'.wbxml':'5','.rl':'6'},'part4':{'.wbdl':'1','.rs':'10'}}}

value = '3'

def find_parent_dict1(Dictionary):
    for key1 in Dictionary.keys():
        item = {key1:key2 for key2 in Dictionary[key1].keys() if value in Dictionary[key1][key2].values()}
        if len(item)>0:
            return item

find_parent_dict1(Dictionary)


def find_parent_dict2(Dictionary):
    for key1 in Dictionary.keys():
        for key2 in Dictionary[key1].keys():
            if value in Dictionary[key1][key2].values():
                print {key1:key2}

find_parent_dict2(Dictionary)
3

这里有一个适用于复杂数据结构的解决方案,这种结构包含了嵌套的列表和字典。

import pprint

def search(d, search_pattern, prev_datapoint_path=''):
    output = []
    current_datapoint = d
    current_datapoint_path = prev_datapoint_path
    if type(current_datapoint) is dict:
        for dkey in current_datapoint:
            if search_pattern in str(dkey):
                c = current_datapoint_path
                c+="['"+dkey+"']"
                output.append(c)
            c = current_datapoint_path
            c+="['"+dkey+"']"
            for i in search(current_datapoint[dkey], search_pattern, c):
                output.append(i)
    elif type(current_datapoint) is list:
        for i in range(0, len(current_datapoint)):
            if search_pattern in str(i):
                c = current_datapoint_path
                c += "[" + str(i) + "]"
                output.append(i)
            c = current_datapoint_path
            c+="["+ str(i) +"]"
            for i in search(current_datapoint[i], search_pattern, c):
                output.append(i)
    elif search_pattern in str(current_datapoint):
        c = current_datapoint_path
        output.append(c)
    output = filter(None, output)
    return list(output)


if __name__ == "__main__":
    d = {'dict1':
             {'part1':
                  {'.wbxml': 'application/vnd.wap.wbxml',
                   '.rl': 'application/resource-lists+xml'},
              'part2':
                  {'.wsdl': 'application/wsdl+xml',
                   '.rs': 'application/rls-services+xml',
                   '.xop': 'application/xop+xml',
                   '.svg': 'image/svg+xml'}},
         'dict2':
             {'part1':
                  {'.dotx': 'application/vnd.openxmlformats-..',
                   '.zaz': 'application/vnd.zzazz.deck+xml',
                   '.xer': 'application/patch-ops-error+xml'}}}

    d2 = {
        "items":
            {
                "item":
                    [
                        {
                            "id": "0001",
                            "type": "donut",
                            "name": "Cake",
                            "ppu": 0.55,
                            "batters":
                                {
                                    "batter":
                                        [
                                            {"id": "1001", "type": "Regular"},
                                            {"id": "1002", "type": "Chocolate"},
                                            {"id": "1003", "type": "Blueberry"},
                                            {"id": "1004", "type": "Devil's Food"}
                                        ]
                                },
                            "topping":
                                [
                                    {"id": "5001", "type": "None"},
                                    {"id": "5002", "type": "Glazed"},
                                    {"id": "5005", "type": "Sugar"},
                                    {"id": "5007", "type": "Powdered Sugar"},
                                    {"id": "5006", "type": "Chocolate with Sprinkles"},
                                    {"id": "5003", "type": "Chocolate"},
                                    {"id": "5004", "type": "Maple"}
                                ]
                        },

                        ...

                    ]
            }
    }

pprint.pprint(search(d,'svg+xml','d'))
>> ["d['dict1']['part2']['.svg']"]

pprint.pprint(search(d2,'500','d2'))
>> ["d2['items']['item'][0]['topping'][0]['id']",
 "d2['items']['item'][0]['topping'][1]['id']",
 "d2['items']['item'][0]['topping'][2]['id']",
 "d2['items']['item'][0]['topping'][3]['id']",
 "d2['items']['item'][0]['topping'][4]['id']",
 "d2['items']['item'][0]['topping'][5]['id']",
 "d2['items']['item'][0]['topping'][6]['id']"]
32

这里有一个简单的递归版本:

def getpath(nested_dict, value, prepath=()):
    for k, v in nested_dict.items():
        path = prepath + (k,)
        if v == value: # found value
            return path
        elif hasattr(v, 'items'): # v is a dict
            p = getpath(v, value, path) # recursive call
            if p is not None:
                return p

示例:

print(getpath(dictionary, 'image/svg+xml'))
# -> ('dict1', 'part2', '.svg')

如果想要得到多个路径(仅适用于Python 3的解决方案):

def find_paths(nested_dict, value, prepath=()):
    for k, v in nested_dict.items():
        path = prepath + (k,)
        if v == value: # found value
            yield path
        elif hasattr(v, 'items'): # v is a dict
            yield from find_paths(v, value, path) 

print(*find_paths(dictionary, 'image/svg+xml'))
10

这段代码是用来逐层遍历你嵌套的字典,并且在遍历的过程中记录下到达某个特定值的所有键。也就是说,一旦你在字典里找到了正确的值,你就已经有了通往这个值所需的所有键。

下面的代码可以直接放到一个 .py 文件里运行。find_mime_type(...) 这个函数会返回一系列的键,这些键可以帮助你从原始字典找到你想要的值。demo() 函数则演示了如何使用这个功能。

d = {'dict1':
         {'part1':
              {'.wbxml': 'application/vnd.wap.wbxml',
               '.rl': 'application/resource-lists+xml'},
          'part2':
              {'.wsdl': 'application/wsdl+xml',
               '.rs': 'application/rls-services+xml',
               '.xop': 'application/xop+xml',
               '.svg': 'image/svg+xml'}},
     'dict2':
         {'part1':
              {'.dotx': 'application/vnd.openxmlformats-..',
               '.zaz': 'application/vnd.zzazz.deck+xml',
               '.xer': 'application/patch-ops-error+xml'}}}


def demo():
    mime_type = 'image/svg+xml'
    try:
        key_chain = find_mime_type(d, mime_type)
    except KeyError:
        print ('Could not find this mime type: {0}'.format(mime_type))
        exit()
    print ('Found {0} mime type here: {1}'.format(mime_type, key_chain))
    nested = d
    for key in key_chain:
        nested = nested[key]
    print ('Confirmation lookup: {0}'.format(nested))


def find_mime_type(d, mime_type):
    reverse_linked_q = list()
    reverse_linked_q.append((list(), d))
    while reverse_linked_q:
        this_key_chain, this_v = reverse_linked_q.pop()
        # finish search if found the mime type
        if this_v == mime_type:
            return this_key_chain
        # not found. keep searching
        # queue dicts for checking / ignore anything that's not a dict
        try:
            items = this_v.items()
        except AttributeError:
            continue  # this was not a nested dict. ignore it
        for k, v in items:
            reverse_linked_q.append((this_key_chain + [k], v))
    # if we haven't returned by this point, we've exhausted all the contents
    raise KeyError


if __name__ == '__main__':
    demo()

输出结果:

在这里找到了 image/svg+xml 的 MIME 类型:['dict1', 'part2', '.svg']

确认查找:image/svg+xml

撰写回答