2017-10-19 6 views
0

Je suis en train de comprendre comment mapper une structure récursive contenant les dictionnaires et les listes, jusqu'à présent, j'ai ceci:Comment mapper une structure récursive?

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

Sortie:

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

Comme vous pouvez le voir, problème avec l'exemple ci-dessus est que rec_map ne renvoie pas une structure correctement mappée, ce que j'essaie d'obtenir est soit la même structure mappée correctement ou un nouveau mappé cloné, par exemple, quelque chose comme ceci:

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

devrait transformer a en:

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

et:

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

dans:

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

Répondre

1

Vous créez un générateur, puis en utilisant yield from, qui aplatit essentiellement. Au lieu de cela, vous aurez envie de matérialiser le générateur au lieu de céder de lui:

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']]]]]] 

Le problème que vous rencontrez est qu'il est beaucoup plus difficile de le faire avec un générateur, parce que vous devez soigneusement ce qui est curate revenu. Honnêtement, il ne semble pas que vous avez besoin, même un générateur, il suffit d'utiliser:

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

En outre, ne pas utiliser collections.Iterable, vérifiez pour Thet ypes explicitement que vous manipulez. Remarque:

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

@BPL c'est le même principe, vous devez le matérialiser dans un dictionnaire ou une liste en fonction du conteneur que vous itérez –

+0

@BPL mis à jour avec une autre approche –

+0

Merci beaucoup, votre solution est vraiment propre sans utiliser de générateurs, je Je vous en donnerais un autre ... mais vous savez, je vous ai déjà donné 1;) – BPL

1

Cela est dû à yield from. Vous devriez plutôt utiliser yield list().

rendement de produire chaque élément du générateur un à la fois, mais ce que vous voulez ici est de donner toute la liste au lieu de chaque élément de celui-ci.

what's the difference between yield from and yield in python 3.3.2+ Cette question explique la différence.

Voici version modifiée du code génère le comportement que vous vouliez:

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

Quel problème avez-vous? Peut-être vérifier si un objet est itérable au lieu de si c'est une liste? –

+0

Pour v dans l, ne faites qu'opérer sur les touches lorsque l est un dict. C'est le problème ici, mais je n'ai pas une solution très propre à la main maintenant. Je vais y penser. –

+0

https://stackoverflow.com/questions/10756427/loop-through-all-nested-dictionary-values ​​Celui-ci est pertinent mais peut ne pas résoudre tous vos problèmes. –