如何映射递归结构?

时间:2017-10-19 17:43:33

标签: python recursion python-3.3

我正在试图弄清楚如何映射包含字典和列表的递归结构,到目前为止我已经得到了这个:

import collections


def rec_walk(l):
    for v in l:
        if isinstance(v, list):
            yield from rec_walk(v)
        else:
            yield v


def rec_map(l, f):
    for v in l:
        if isinstance(v, collections.Iterable):
            if isinstance(v, list):
                yield list(rec_map(v, f))
            elif isinstance(v, dict):
                yield dict(rec_map(v, f))
        else:
            yield f(v)


a = ["0", ["1", "2", ["3", "4"]], [[[[["5"]]]]]]
print(list(rec_map(a, lambda x: x + "_tweaked")))
b = {
    'a': ["0", "1"],
    'b': [[[[[["2"]]]]]],
    'c': {
        'd': [{
            'e': [[[[[[["3"]]]]]]]
        }]
    }
}
print(dict(rec_map(b, lambda x: x + "_tweaked")))

输出:

[[[]], [[[[[]]]]]]
{}

正如您所看到的,上面示例的问题是rec_map没有返回正确映射的结构,我想要获得的是正确映射的相同结构或新的克隆映射结构,例如,类似这样:

a = ["0", ["1", "2", ["3", "4"]], [[[[["5"]]]]]]
rec_map(a, lambda x: x + "_tweaked")

应将a转换为:

["0_tweaked", ["1_tweaked", "2_tweaked", ["3_tweaked", "4_tweaked"]], [[[[["5_tweaked"]]]]]]

b = {
    'a': ["0", "1"],
    'b': [[[[[["2"]]]]]],
    'c': {
        'd': [{
            'e': [[[[[[["3"]]]]]]]
        }]
    }
}
print(dict(rec_map(b, lambda x: x + "_tweaked")))

成:

b = {
    'a': ["0_tweaked", "1_tweaked"],
    'b': [[[[[["2_tweaked"]]]]]],
    'c': {
        'd': [{
            'e': [[[[[[["3_tweaked"]]]]]]]
        }]
    }
}

2 个答案:

答案 0 :(得分:1)

这是由于yield from。您应该使用yield list()代替。

从每个元素一次一个地生成每个元素,但是你想要的是产生整个列表而不是它的每个元素。

what's the difference between yield from and yield in python 3.3.2+这个问题解释了差异。

以下修改后的代码版本会生成您想要的行为:

def rec_walk(l):
    for v in l:
        if isinstance(v, list):
            yield list(rec_walk(v))
        else:
            yield v


def rec_map(l, f):
    for v in l:
        if isinstance(v, list):
            yield list(rec_map(v, f))
        else:
            yield f(v)


a = ["0", ["1", "2", ["3", "4"]], [[[[["5"]]]]]]
print('-' * 80)
print(list(rec_walk(a)))
print('-' * 80)
print(list(rec_map(a, lambda x: x + "_tweaked")))

答案 1 :(得分:1)

您正在创建一个生成器,然后使用yield from,它基本上变平。相反,你需要实现生成器而不是从它产生:

In [1]: def rec_map(l, f):
   ...:     for v in l:
   ...:         if isinstance(v, list):
   ...:             yield list(rec_map(v, f))
   ...:         else:
   ...:             yield f(v)
   ...:

In [2]: a = ["0", ["1", "2", ["3", "4"]], [[[[["5"]]]]]]
   ...:

In [3]: list(rec_map(a, lambda x: x + "_tweaked"))
Out[3]:
['0_tweaked',
 ['1_tweaked', '2_tweaked', ['3_tweaked', '4_tweaked']],
 [[[[['5_tweaked']]]]]]

您遇到的问题是使用生成器执行此操作要困难得多,因为您必须仔细策划返回的内容。老实说,看起来你甚至不需要发电机,只需使用:

In [16]: def rec_map(l, f):
    ...:     if isinstance(l, list):
    ...:         return [rec_map(v, f) for v in l]
    ...:     elif isinstance(l, dict):
    ...:         return {k:rec_map(v, f) for k,v in l.items()}
    ...:     else:
    ...:         return f(l)
    ...:

In [17]: rec_map(b, lambda x: x + '_tweaked')
Out[17]:
{'a': ['0_tweaked', '1_tweaked'],
 'b': [[[[[['2_tweaked']]]]]],
 'c': {'d': [{'e': [[[[[[['3_tweaked']]]]]]]}]}}

此外,请勿使用collections.Iterable,请明确检查您正在处理的ypes。注意:

In [18]: isinstance('I am a string but I am iterable!', collections.Iterable)
Out[18]: True