2017-10-19 88 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這一個是相關的,但可能無法解決你所有的問題。 –