使用嵌套键对Dict进行Python递归搜索

时间:2016-04-23 08:03:20

标签: python list dictionary recursion global

我最近不得不用嵌套的字典/列表组合解决实际数据系统中的问题。我在这方面工作了很长一段时间并想出了一个解决方案,但我非常不满意。我不得不求助于使用globals()和一个命名的临时全局参数。

我不喜欢使用全局变量。这只是要求注射漏洞。我觉得必须有更好的方法来执行这项任务而不诉诸全局变量。

问题数据集:

d = {
    "k":1,
    "stuff":"s1",
    "l":{"m":[
        {
            "k":2,
            "stuff":"s2",
            "l":None
        },
        {
            "k":3,
            "stuff":"s3",
            "l":{"m":[
                {
                    "k":4,
                    "stuff":"s4",
                    "l":None
                },
                {
                    "k":5,
                    "stuff":"s5",
                    "l":{"m":[
                        {
                            "k":6,
                            "stuff":"s6",
                            "l":None
                        },
                    ]}
                },
            ]}
        },
    ]}
}

期望的输出:

[{'k': 1, 'stuff': 's1'},
 {'k': 2, 'stuff': 's2'},
 {'k': 3, 'stuff': 's3'},
 {'k': 4, 'stuff': 's4'},
 {'k': 5, 'stuff': 's5'},
 {'k': 6, 'stuff': 's6'}]

我的解决方案:

def _get_recursive_results(d, iter_key, get_keys):
    if not 'h' in globals():
        global h
        h = []
    h.append({k:d.get(k) for k in get_keys})

    d2 = d.copy()
    for k in iter_key:
        if not d2:
            continue
        d2 = d2.get(k)

    for td in d2:
        d3 = td.copy()
        for k in iter_key:
            if not d3:
                continue
            d3 = d3.get(k)

        if d3:
            return _get_recursive_results(td, iter_key, get_keys)
        h.append({k:td.get(k) for k in get_keys})
    else:
        l = [k for k in h]
        del globals()['h']
        return l

按如下方式调用我的函数会返回所需的结果:

_get_recursively(d, ['l','m'], ['k','stuff'])

我如何构建更好的解决方案?

6 个答案:

答案 0 :(得分:7)

这是一个稍微修改过的版本,没有使用全局变量。将h设为None 默认情况下,为第一次调用_get_recursive_results()创建一个新列表。稍后在h的递归调用中提供_get_recursive_results()作为参数:

def _get_recursive_results(d, iter_key, get_keys, h=None):
    if h is None:
        h = []
    h.append({k:d.get(k) for k in get_keys})
    d2 = d.copy()
    for k in iter_key:
        if not d2:
            continue
        d2 = d2.get(k)
    for td in d2:
        d3 = td.copy()
        for k in iter_key:
            if not d3:
                continue
            d3 = d3.get(k)
        if d3:
            return _get_recursive_results(td, iter_key, get_keys, h)
        h.append({k:td.get(k) for k in get_keys})
    else:
        l = [k for k in h]
        return l

现在:

>>> _get_recursive_results(d, ['l','m'], ['k','stuff'])
[{'k': 1, 'stuff': 's1'},
 {'k': 2, 'stuff': 's2'},
 {'k': 3, 'stuff': 's3'},
 {'k': 4, 'stuff': 's4'},
 {'k': 5, 'stuff': 's5'},
 {'k': 6, 'stuff': 's6'}]

不需要复制中间词。这是一个没有复制的进一步修改版本:

def _get_recursive_results(d, iter_key, get_keys, h=None):
    if h is None:
        h = []
    h.append({k: d.get(k) for k in get_keys})
    for k in iter_key:
        if not d:
            continue
        d = d.get(k)
    for td in d:
        d3 = td
        for k in iter_key:
            if not d3:
                continue
            d3 = d3.get(k)
        if d3:
            return _get_recursive_results(td, iter_key, get_keys, h)
        h.append({k: td.get(k) for k in get_keys})
    else:
        return h

答案 1 :(得分:5)

这不是通用的,但可以完成工作:

def parse_tree(d, keys):
   result = [{key: d[key] for key in keys}]
   l = d.get('l', None)
   if l is not None:
       entries = l.get('m', [])
       for entry in entries:
           result.extend(parse_tree(entry))
   return result


>>> parse_tree(d, ['k', 'stuff'])
[{'k': 1, 'stuff': 's1'},
 {'k': 2, 'stuff': 's2'},
 {'k': 3, 'stuff': 's3'},
 {'k': 4, 'stuff': 's4'},
 {'k': 5, 'stuff': 's5'},
 {'k': 6, 'stuff': 's6'}]

答案 2 :(得分:4)

使用生成器

使用以下生成器:

def get_stuff(dct, iter_keys, get_keys):
    k, stuff = get_keys
    l, m = iter_keys
    if k in dct:
        yield {k: dct[k], stuff: dct[stuff]}
        if dct.get(l):
            for subdct in dct[l][m]:
                for res in get_stuff(subdct, iter_keys, get_keys):
                    yield res


list(get_stuff(d, ["l", "m"], ["k", "stuff"]))

你得到的结果是:

list(get_stuff(d))

Python 3.3提供了用于委托屈服于子生成器的新yield from表达式。使用此表达式,代码可以缩短一行:

def get_stuff(dct):
    if "k" in dct:
        yield {"k": dct["k"], "stuff": dct["stuff"]}
        if dct.get("l"):
            for subdct in dct["l"]["m"]:
                yield from get_stuff(subdct)

def get_stuff(dct, iter_keys, get_keys):
    k, stuff = get_keys
    l, m = iter_keys
    if k in dct:
        yield {k: dct[k], stuff: dct[stuff]}
        if dct.get(l):
            for subdct in dct[l][m]:
                yield from get_stuff(subdct, iter_keys, get_keys):

要避免globals

的一些方法

发电机

通常,如果您需要构建列表并搜索替换全局变量,则生成器可能会 方便,因为他们将当前工作的状态保持在局部变量中,加上构建整个结果会推迟到消耗生成的值。

递归

递归将子结果存储在堆栈中的局部变量中。

具有内部属性的类实例

一个类可以作为封装变量的锡。

您可以将中间结果存储在实例属性中,而不是使用全局变量。

针对不同的数据结构进行通用

在您提到的评论中,每次转储都会收到许多不同的类型。

我认为您的数据符合以下期望:

  • 具有树状结构
  • 树中的每个节点都应对结果产生一些结果(例如字典{"k": xx, "stuff": yy}
  • 每个节点可能包含子项(子节点列表)

使解决方案更加通用的一个选项是提供要使用的密钥列表 要访问值/子项,另一个选项是提供一个函数 获取节点值和子项的工作。

在这里,我使用get_value来传递节点值,并使用get_subitems来传递子节点:

def get_value(data):
    try:
        return {"k": data["k"], "stuff": data["stuff"]}
    except KeyError:
        return None


def get_subitems(data):
    try:
        return data["l"]["m"]
    except TypeError:
        return None

然后通过以下方式完成处理:

def get_stuff(dct, get_value_fun, get_subitems_fun):
    value = get_value(dct)
    if value:
        yield value
        lst = get_subitems_fun(dct)
        if lst:
            for subdct in lst:
                for res in get_stuff(subdct, get_value_fun, get_subitems_fun):
                    yield res

以这种方式打电话:

get_stuff(d, get_value, get_subitems)

使用函数的优点在于它对于任何数据都更加灵活 您必须处理的结构(适应其他数据结构只需要提供自定义版本的函数get_valueget_subitems - 根据您的偏好设置相同或不同的名称。

答案 3 :(得分:3)

编辑:第一个版本有一个现已更正的错误

我相信这应该有效,我们正在使用递归的力量!

def strip_leaves_from_tree(my_tree):
    result = list()
    row = dict()
    for key in my_tree:
        child = my_tree[key]
        if type(child) in (int, str,):
            row[key] = child
        elif isinstance(child, dict):
            result = strip_leaves_from_tree(child)
        elif isinstance(child, list):
            for element in child:
                result += strip_leaves_from_tree(element)
    if row: result = [row,]+result
    return result

答案 4 :(得分:2)

我确认它有效。请检查一下。 当然,在更改字典列表的结构时应该修改它。

def add(ret, val):
  if val is not None: ret.append(val)

def flatten(d, ret):
  for k,v in d.items():
    if isinstance(v, dict): add(ret,flatten(v, ret))
    elif isinstance(v, list):
        for i in v: add(ret, flatten(i, ret))
    elif k=='k':
        ret.append({'k':v,'stuff':d.get('stuff')})

ret = []
flatten(d, ret)

答案 5 :(得分:1)