如何在Python中产生递归函数

时间:2018-10-09 16:33:37

标签: python python-3.x

所以我有一本字典:

{'a': {'b': {'c': 'd', 'e': 'f'}}}

我需要创建一个字典,如下所示:

{'c':'d', 'e','f'}

它可以更深入到任何级别,但我应该始终以最大深度获得键值对。所以我写了一个函数:

def boil_down_array(key, data):
    if type(data) == dict:
        for key, item in data.items():
            boil_down_array(key, item)
    else:
        yield {key:data}

现在的问题是,一旦递归,yield就丢失了。我如何再次产生该词典?我所得到的只是一个发电机,这不是我想要的。

3 个答案:

答案 0 :(得分:5)

在递归调用中使用yield from,否则您将忽略递归调用的结果:

def boil_down_array(key, data):
    if type(data) == dict:
        for key, item in data.items():
            yield from boil_down_array(key, item)
    else:
        yield {key: data}

这仅在Python> 3.3中可用,但本质上只是简单地从额外循环中产生的简捷方式:

for key, item in data.items():
    for x in boil_down_array(key, item):  # just exhaust the recursive generator
        yield x  # and "re-yield" what it produces

为了获得所需的数据结构,最好放弃对,而不是dicts,这样就可以更轻松地将结果转换为结果dict

yield key, data

然后您可以像使用它一样

result = dict(boil_down_array(None, input_dict))

一种更简单的递归方法将只返回完整的dict

def boil_down_nested(dct):
    result = {}
    for k, v in dct.items():
        if isinstance(v, dict):
            result.update(boil_down_nested(v))
        else:
            result[k] = v
    return result

答案 1 :(得分:4)

您将忽略递归调用生成的生成器对象:

for key, item in data.items():
    boil_down_array(key, item)  # creates a generator object

因此实际上不会执行递归调用(生成器中的代码永远不会为该调用执行)。

您要使用yield fromdelegate iteration进行通话:

for key, item in data.items():
    yield from boil_down_array(key, item)

yield from将控制从当前生成器移至yield from之后的表达式生成的迭代器;这就是您的递归生成器。

yield from需要Python 3.3或更高版本。如果您使用的是Python 2或更旧的Python 3版本,则还可以添加另一个循环以显式产生迭代产生的每个结果:

for key, item in data.items():
    for result in boil_down_array(key, item):
        yield result

我也将使用isinstance(data, dict)而不是type(...) ==来允许子类:

def boil_down_array(key, data):
    if isinstance(data, dict):
        for key, item in data.items():
            yield from boil_down_array(key, item)
    else:
        yield {key: data}

请注意,您的代码实际上并不产生字典作为输出。它会生成单个键值字典的 iterable

>>> d = {'a': {'b': {'c': 'd', 'e': 'f'}}}
>>> list(boil_down_array('v', d))
[{'c': 'd'}, {'e': 'f'}]

最外面的调用中的key参数在这里也是多余的,因为您将其替换为当前迭代的键。

如果您确实需要使用生成器函数,那么至少要生成(key, value)元组,并且在 value 不是字典的情况下也不必进行递归操作(因此请在进行测试之前,递归),以消除传递密钥的需要;现在假定其余data自变量是字典,始终:

def boil_down_nested(data):
    for key, value in data.items():
        if isinstance(value, dict):
            yield from boil_down_nested(value)
        else:
            yield (key, value)

并使用dict(boil_down_nested(input_dict))从生成器现在输出的键值元组中生成一个新字典:

>>> next(boil_down_nested(d))  # first resulting key-value pair
('c', 'd')
>>> dict(boil_down_nested(d))  # all key-value pairs into a dictionary.
{'c': 'd', 'e': 'f'}

没有递归,您可以使用堆栈来跟踪仍要处理的嵌套字典;这使得直接输出字典作为结果变得容易得多:

def boil_down_nested_dict(d):
    stack = [d]
    output = {}
    while stack:
        for key, value in stack.pop().items():
            if isinstance(value, dict):
                stack.append(value)  # process this value next
            else:
                output[key] = value
    return output

不再需要单独的dict()呼叫:

>>> boil_down_nested_dict(d)
{'c': 'd', 'e': 'f'}

答案 2 :(得分:0)

请注意,您不一定需要使用yield

def last(d):
  c = [i for b in d.items() for i in ([b] if not isinstance(b[-1], dict) else last(b[-1]))]
  return c

print(dict(last({'a': {'b': {'c': 'd', 'e': 'f'}}})))

输出:

{'c': 'd', 'e': 'f'}