2017-10-19 67 views
0

我试图找出如何映射同时包含字典和列表递归结构,到目前为止,我得到这个:如何映射递归结构?

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"]]]]]]] 
     }] 
    } 
} 

回答

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 
+0

@BPL它是一样的原则,你需要实现它到一个字典或列表取决于你正在迭代的容器 –

+0

@BPL更新与另一种方法 –

+0

感谢一堆,你的解决方案是一个非常干净的没有使用发电机,我'给你另一个喜欢......但你知道,我已经给你1;) – BPL

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"))) 
+0

你有什么问题吗?也许检查一个对象是否可迭代,而不是它是否是一个列表? –

+0

for v in l仅当l是字典时迭代键。这是问题所在,但我现在手头没有一个很干净的解决方案。我会考虑一下。 –

+0

https://stackoverflow.com/questions/10756427/loop-through-all-nested-dictionary-values这一个是相关的,但可能无法解决你所有的问题。 –