1
votes

Fusionner deux listes de dictionnaires en fonction d'une condition

J'ai deux listes de dictionnaires et je dois les fusionner chaque fois que USA et GOOG sont identiques.

from collections import defaultdict
dics = list1+list2

for dic in dics:
    for key, val in dic.items():
        dd[key].append(val)            

for dic in dics:
    for key, val in dic.items(): 
        dd[key].append(val)

Puisque USA et GOOG avaient les mêmes valeurs pour le 2ème élément de list1 et le 1er élément de list2 , ils devraient donc fusionné. Le résultat attendu est le suivant -

Result = 
[{'USA': 'Eastern', 
  'GOOG': '2019', 
  'Up': {'Upfront': 45}, 
  'Right': {'Upfront': 12}}, 

 {'USA': 'Western', 
  'GOOG': '2019', 
  'Up': {'Upfront': 10}, 
  'Down': {'Downback': 35}, 
  'Right': {'Upfront': 15, 'Downback': 25}},

 {'USA': 'Eastern', 
  'GOOG': '2018', 
  'Down': {'Downback': 15}, 
  'Right': {'Downback': 55}}]

Comment pouvons-nous écrire un code générique pour cela. J'ai essayé d'utiliser defaultdict , mais je ne savais pas comment concaténer un nombre arbitraire de reste des dictionnaires.

Ma tentative:

list1 = 
[{'USA': 'Eastern', 
  'GOOG': '2019', 
  'Up': {'Upfront': 45}, 
  'Right': {'Upfront': 12}}, 

 {'USA': 'Western', 
  'GOOG': '2019', 
  'Up': {'Upfront': 10}, 
  'Right': {'Upfront': 15}}]

list2=
[{'USA': 'Western', 
  'GOOG': '2019', 
  'Down': {'Downback': 35}, 
  'Right': {'Downback': 25}}, 

 {'USA': 'Eastern', 
  'GOOG': '2018', 
  'Down': {'Downback': 15}, 
  'Right': {'Downback': 55}}]


5 commentaires

Pouvez-vous partager ce que vous avez déjà essayé et où vous rencontrez des problèmes?


@AmitNanaware J'ai essayé plusieurs choses, mais je poste un petit extrait de code ...


@meowgoesthedog Merci mec


Quelle version de Python utilisez-vous? Python 3.x a de nouvelles commodités pertinentes à cet égard.


@meowgoesthedog 3. + Version. J'ai essayé de suivre stackoverflow.com/questions/20694681/... la solution par iruvar


3 Réponses :


1
votes

Voici ma tentative. Je ne sais pas si c'est la meilleure façon, mais c'est un début.

Étapes:

  • combiner des listes de dictionnaires
  • créer une collection triée des valeurs pertinentes et indexer dans une liste combinée
  • regrouper par les valeurs pertinentes
  • parcourir les clés et les groupes en ajoutant le dictionnaire s'il n'apparaît qu'une seule fois en fonction des correspondances de valeurs ou mettre à jour un dictionnaire s'il apparaît plus d'une fois en fonction des correspondances de valeurs

Code:

import operator as op
import itertools as it
from functools import reduce
from pprint import pprint

dictionaries = reduce(op.add, (list1, list2,))
groups = it.groupby(sorted([(op.itemgetter('USA', 'GOOG')(d), i)
                            for i, d in enumerate(dictionaries)]),
                    key=op.itemgetter(0))
results = []
for key, group in groups:
    _, indices = zip(*group)
    if len(indices) == 1:
        i, = indices
        results.append(dictionaries[i])
    else:
        merge = dictionaries[indices[0]]
        for i in indices[1:]:
            merge.update(dictionaries[i])
        results.append(merge)
pprint(results, indent=4)

OUTPUT:

[{'Down': {'Downback': 15}, «GOOG»: «2018», 'Droite': {'Downback': 55}, 'USA': 'Est'}, {'GOOG': '2019', 'Droite': {'Upfront': 12}, «USA»: «Est», 'Up': {'Upfront': 45}}, {'Down': {'Downback': 35}, «GOOG»: «2019», 'Droite': {'Downback': 25}, «USA»: «occidental», 'Up': {'Upfront': 10}}]


6 commentaires

J'ai supposé que vous pourriez avoir une variable avec un grand nombre de listes. Vous pouvez toujours utiliser l'opérateur infixe + , même si vous devrez taper un + pour chaque élément.


Le reduction (op.add, (l1, l2)) est redondant - c'est exactement la même chose que de simplement concaténer les listes avec + , mais beaucoup plus compliqué à lire ( et aura probablement également un impact sur les performances pour les listes plus volumineuses): dictionaries = list1 + list2


De plus, votre solution n'envisage pas de fusionner les clés résultantes s'il y a des valeurs pour then dans les deux enregistrements - voir la valeur de résultat de l'opération pour WESTERN ',' 2019 ',' Right ': {' Upfront ': 15, 'Downback': 25}}, `


Oui. J'ai mal compris que les dictionnaires imbriqués doivent également être fusionnés.


oui - en attendant, ma solution (sur ma propre réponse) fournissait une intersection au lieu de fusionner les deux listes - corrigée maintenant.


@DMfll J'ai vérifié votre solution, même si cela n'a pas abouti à la réponse souhaitée, mais l'effort que vous avez déployé est le plus important. Merci beaucoup pour cela :)



2
votes

Il y a deux tâches algorithmiques dont vous avez besoin: trouver les enregistrements qui ont les mêmes valeurs pour USA et GOOGL, puis les joindre et faire cela de manière à ce que si la même clé existe dans les deux enregistrements, leur valeur soit fusionnée .

L'approche naïve pour la première serait d'avoir une boucle for qui itérerait les valeurs de list1, et pour chaque valeur, itérerait toutes les valeurs de list2 - deux boucles séparées ne la couperont pas, vous en auriez besoin de deux imbriqué pour les boucles :

from copy import deepcopy

def merge_lists(list1, list2):
    # create dictionary from list1:
    dict1 = {(record["GOOG"], record["USA"]): record  for record in list1}

    #compare elements in list2 to those on list1:

    result = {}
    for record in list2:
        ckey = record["GOOG"], record["USA"]
        new_record = deepcopy(record)
        if ckey in dict1:
            for key, value in dict1[ckey].items():
                if key in ("GOOG", "USA"):
                    # Do not merge these keys
                    continue
                # Dict's "setdefault" finds a key/value, and if it is missing
                # creates a new one with the second parameter as value
                new_record.setdefault(key, {}).update(value)

        result[ckey] = new_record

    # Add values from list1 that were not matched in list2:
    for key, value in dict1.items():
        if key not in result:
            result[key] = deepcopy(value)

    return list(result.values())

Bien que cette approche fonctionne, et convient parfaitement aux petites listes (

Donc, une bonne solution est d'en recréer une des listes de manière à ce que la clé de comparaison soit utilisée comme hachage - ce qui est fait en copiant la liste dans un dictionnaire où les clés sont les valeurs que vous souhaitez comparer, puis en effectuant une itération sur la deuxième liste une seule fois. Comme les dictionnaires ont un temps constant pour trouver des éléments, cela rendra le nombre de comparaisons proportionnel à la taille de votre liste.

La deuxième partie de votre tâche consiste à comparer pour copier un enregistrement dans une liste de résultats, et mettre à jour les clés sur la copie résultante afin que toutes les clés dupliquées soient fusionnées. Pour éviter un problème lors de la copie des premiers enregistrements, nous sommes plus sûrs d'utiliser copy.deepcopy de Python, qui garantira que les sous-dictionnaires sont des objets différents de ceux de l'enregistrement d'origine et resteront isolés.

for element in list1:
    for other_element in list2:
        if ...:
            ...


1 commentaires

Merci beaucoup pour une solution rapide et vos efforts. Cela fonctionne parfaitement et résout le problème actuel. Apprécié :)



1
votes

Voici ma tentative de solution. Il parvient à reproduire les résultats que vous avez demandés. Veuillez ignorer à quel point mes variables sont mal nommées. J'ai trouvé ce problème assez intéressant.

def joinListByDictionary(list1, list2):
    """Join lists on USA and GOOG having the same value"""
    list1.extend(list2)
    matchIndx = []
    matches = []    

    for dicts in range(len(list1)):
        for dicts2 in range(len(list1)):
            if dicts == dicts2:
                continue
            if list1[dicts]["GOOG"] == list1[dicts2]["GOOG"] and list1[dicts]["USA"] == list1[dicts2]["USA"]:

                matches.append(list1[dicts])
                matchIndx.append(dicts) 
    for dictz in matches:
        for dictzz in matches:
            for key in dictz.keys():
                if key in dictzz.keys() and isinstance(dictzz[key], dict):
                    dictzz[key].update(dictz[key])          
        matches.remove(dictz)

    newList = [list1[ele] for ele in range(len(list1)) if ele not in matchIndx]
    newList.extend(matches)
    print newList
    return newList       

joinListByDictionary(list1, list2)


0 commentaires