Comme indiqué dans de nombreuses autres réponses, un algorithme récursif est le plus logique dans ce cas. En général, lorsqu'on travaille avec la récursivité, il est préférable de créer de nouvelles valeurs plutôt que d'essayer de modifier une structure de données d'entrée.
Nous devons définir ce qui se passe à chaque étape de la fusion. Si les deux entrées sont des dictionnaires, c'est facile : nous copions les clés uniques de chaque côté, et fusionnons récursivement les valeurs des clés dupliquées. Ce sont les cas de base qui posent problème. Il sera plus facile de comprendre la logique si nous créons une fonction distincte pour cela. En guise de substitut, nous pourrions simplement envelopper les deux valeurs dans un tuple :
def merge_leaves(x, y):
return (x, y)
Le cœur de notre logique est maintenant le suivant :
def merge(x, y):
if not(isinstance(x, dict) and isinstance(y, dict)):
return merge_leaves(x, y)
x_keys, y_keys = x.keys(), y.keys()
result = { k: merge(x[k], y[k]) for k in x_keys & y_keys }
result.update({k: x[k] for k in x_keys - y_keys})
result.update({k: y[k] for k in y_keys - x_keys})
return result
Testons-le :
>>> x = {'a': {'b': 'c', 'd': 'e'}, 'f': 1, 'g': {'h', 'i'}, 'j': None}
>>> y = {'a': {'d': 'e', 'h': 'i'}, 'f': {'b': 'c'}, 'g': 1, 'k': None}
>>> merge(x, y)
{'f': (1, {'b': 'c'}), 'g': ({'h', 'i'}, 1), 'a': {'d': ('e', 'e'), 'b': 'c', 'h': 'i'}, 'j': None, 'k': None}
>>> x # The originals are unmodified.
{'a': {'b': 'c', 'd': 'e'}, 'f': 1, 'g': {'h', 'i'}, 'j': None}
>>> y
{'a': {'d': 'e', 'h': 'i'}, 'f': {'b': 'c'}, 'g': 1, 'k': None}
Nous pouvons facilement modifier la règle de fusion des feuilles, par exemple :
def merge_leaves(x, y):
try:
return x + y
except TypeError:
return Ellipsis
et observer les effets :
>>> merge(x, y)
{'f': Ellipsis, 'g': Ellipsis, 'a': {'d': 'ee', 'b': 'c', 'h': 'i'}, 'j': None, 'k': None}
Nous pourrions aussi potentiellement nettoyer cela en utilisant une bibliothèque tierce pour distribuer en fonction du type des entrées. Par exemple, en utilisant multipledispatch Nous pourrions faire des choses comme.. :
@dispatch(dict, dict)
def merge(x, y):
x_keys, y_keys = x.keys(), y.keys()
result = { k: merge(x[k], y[k]) for k in x_keys & y_keys }
result.update({k: x[k] for k in x_keys - y_keys})
result.update({k: y[k] for k in y_keys - x_keys})
return result
@dispatch(str, str)
def merge(x, y):
return x + y
@dispatch(tuple, tuple)
def merge(x, y):
return x + y
@dispatch(list, list)
def merge(x, y):
return x + y
@dispatch(int, int):
def merge(x, y):
raise ValueError("integer value conflict")
@dispatch(object, object):
return (x, y)
Cela nous permet de gérer diverses combinaisons de cas spéciaux de type feuille sans écrire notre propre vérification de type, et remplace également la vérification de type dans la fonction récursive principale.