2011-06-13 121 views
11

¿Cómo comparo dos listas de dict? El resultado debe ser los impares fuera de la lista de dict B.¿Cómo comparar dos listas de dicts en Python?

Ejemplo:

ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9}, 
     {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}] 


ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7}, 
     {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5}, 
     {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}] 

Aquí quiero comparar con LDA LDB. Debería imprimir el resultado a continuación.

ldB -> {user:"nameA", b:99.9, d:43.7} 
ldB -> {user:"nameB", a:67.7, c:1.1 } 
ldb -> {user:"nameC", a:89.9, b:77.3, c:2.2, d:6.5} 

He pasado por el siguiente enlace, pero allí solo devuelve el nombre, pero quiero nombre y valor como el anterior.

List of Dicts comparision to match between lists and detect value changes in Python

+0

No hay diff jerárquica de estructuras arbitrarias, por lo que necesita para escribir un algoritmo algo más complicado basado en lo que sabes sobre tus datos. ¿El 'usuario' es una clave especial? ¿Se usa para establecer una correspondencia entre elementos en las listas (supongamos que 'ldB' está fuera de servicio, si el resultado es el mismo)? –

+0

sí, clave especial del usuario aquí – newbe

+0

Probablemente tenga más sentido, para el resto del programa así como aquí, tener estructuras más parecidas a 'ldA = {'userA': {'a': 1, 'b': 2, ...}, ...} '. –

Respuesta

7

Para una solución general, considere lo siguiente. Diferirá correctamente, incluso si los usuarios están fuera de servicio en las listas.

def dict_diff (merge, lhs, rhs): 
    """Generic dictionary difference.""" 
    diff = {} 
    for key in lhs.keys(): 
      # auto-merge for missing key on right-hand-side. 
     if (not rhs.has_key(key)): 
      diff[key] = lhs[key] 
      # on collision, invoke custom merge function. 
     elif (lhs[key] != rhs[key]): 
      diff[key] = merge(lhs[key], rhs[key]) 
    for key in rhs.keys(): 
      # auto-merge for missing key on left-hand-side. 
     if (not lhs.has_key(key)): 
      diff[key] = rhs[key] 
    return diff 

def user_diff (lhs, rhs): 
    """Merge dictionaries using value from right-hand-side on conflict.""" 
    merge = lambda l,r: r 
    return dict_diff(merge, lhs, rhs) 

import copy 

def push (x, k, v): 
    """Returns copy of dict `x` with key `k` set to `v`.""" 
    x = copy.copy(x); x[k] = v; return x 

def pop (x, k): 
    """Returns copy of dict `x` without key `k`.""" 
    x = copy.copy(x); del x[k]; return x 

def special_diff (lhs, rhs, k): 
     # transform list of dicts into 2 levels of dicts, 1st level index by k. 
    lhs = dict([(D[k],pop(D,k)) for D in lhs]) 
    rhs = dict([(D[k],pop(D,k)) for D in rhs]) 
     # diff at the 1st level. 
    c = dict_diff(user_diff, lhs, rhs) 
     # transform to back to initial format. 
    return [push(D,k,K) for (K,D) in c.items()] 

A continuación, se puede comprobar la solución:

ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9}, 
     {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}] 
ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7}, 
     {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5}, 
     {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}] 
import pprint 
if __name__ == '__main__': 
    pprint.pprint(special_diff(ldA, ldB, 'user')) 
+0

Como Karl señaló en su respuesta, deberá reemplazar la comparación '! =' En la función 'dict_diff' por un operador de comparación personalizado, ya que estás comparando valores de coma flotante. O, en este caso, puede reemplazar el 'lambda l, r: r' por' min' o 'max' (o lo que se ajuste a sus necesidades). –

+0

¡Ahora que es de fuerza industrial! Creo que la llamada a 'merge' en' dict_diff' se supone que es 'user_diff'. –

+0

@Karl: funciona como se anuncia, realmente lo pruebo todo. La función de fusión es 'user_diff', ya que eso es lo que se pasa a' dict_diff' en 'special_diff'. Esta indirección permite usar el mismo algoritmo para diferir las listas y diferenciar los usuarios individuales. –

2

Voy a asumir que los correspondientes dict s están en el mismo orden en ambas listas.

Bajo este supuesto, se puede utilizar el siguiente código:

def diffs(L1, L2): 
    answer = [] 
    for i, d1 in enumerate(L1): 
     d = {} 
     d2 = L2[i] 
     for key in d1: 
      if key not in d1: 
       print key, "is in d1 but not in d2" 
      elif d1[key] != d2[key]: 
       d[key] = d2[key] 
     answer.append(d) 
    return answer 

No probado. Por favor comente si hay errores y los

+0

Primero me gustaría darle las gracias por la respuesta, aquí solo se muestran valores diferentes, pero necesito valores diferentes específicos del usuario de ldB – newbe

+0

¿qué quiere decir "específico del usuario"? ¿Quiere decir que quiere comparar diccionarios donde el valor de "Usuario" es el mismo o quiere decir que quiere comparar solo ciertas teclas que se proporcionarán como entrada a la función? – inspectorG4dget

3

fijaré Mi enfoque: construir una búsqueda basada en los valores de LDA para excluir, a continuación, determinar el resultado de la exclusión de los valores apropiados de cada lista en LDB.

lookup = dict((x['user'], dict(x)) for x in ldA) 
# 'dict(x)' is used here to make a copy 
for v in lookup.values(): del v['user'] 

result = [ 
    dict(
     (k, v) 
     for (k, v) in item.items() 
     if item['user'] not in lookup or lookup[item['user']].get(k, v) == v 
    ) 
    for item in ldB 
] 

You should, however, be aware that comparing floating-point values like that can't be relied upon.

+0

gracias por la respuesta +1 – newbe

0

Esto definitivamente toma algunas suposiciones de sus datos de muestra, principalmente que no habrá usuarios en ldA que no estén en ldB, si esta es una suposición no válida déjenme saber.

Se podría llamar a esto como dict_diff(ldA, ldB, user).

def dict_diff(ldA, ldB, key): 
    for i, dA in enumerate(ldA): 
     d = {key: dA[key]} 
     d.update(dict((k, v) for k, v in ldB[i].items() if v != dA[k])) 
     print "ldB -> " + str(d) 
    for dB in ldB[i+1:]: 
     print "ldB -> " + str(dB) 
+0

+1, muchas gracias por responder – newbe

1

Uno más solución un poco raro (lo siento si he perdido algo), sino que también le permite configurar su propia verificación de la igualdad (sólo hay modificar IsEqual lambda para esto), así como darle dos opciones diferentes sobre cómo hacer frente en caso de que las llaves se diferencian:

ldA = [{'user':"nameA", 'a':7.6, 'b':100.0, 'c':45.5, 'd':48.9}, 
     {'user':"nameB", 'a':46.7, 'b':67.3, 'c':0.0, 'd':5.5}] 


ldB =[{'user':"nameA", 'a':7.6, 'b':99.9, 'c':45.5, 'd':43.7}, 
     {'user':"nameB", 'a':67.7, 'b':67.3, 'c':1.1, 'd':5.5}, 
     {'user':"nameC", 'a':89.9, 'b':77.3, 'c':2.2, 'd':6.5}] 

ldA.extend((ldB.pop() for i in xrange(len(ldB)))) # get the only one list here 

output = [] 

isEqual = lambda x,y: x != y # add your custom equality check here, for example rounding values before comparison and so on 

while len(ldA) > 0: # iterate through list 
    row = ldA.pop(0) # get the first element in list and remove it from list 
    for i, srow in enumerate(ldA): 
     if row['user'] != srow['user']: 
      continue 
     res = {'user': srow['user']} # 
     # next line will ignore all keys of srow which are not in row 
     res.update(dict((key,val) for key,val in ldA.pop(i).iteritems() if key in row and isEqual(val, row[key]))) 
     # next line will include the srow.key and srow.value into the results even in a case when there is no such pair in a row 
     #res.update(dict(filter(lambda d: isEqual(d[1], row[d[0]]) if d[0] in row else True ,ldA.pop(i).items()))) 
     output.append(res) 
     break 
    else: 
     output.append(row) 

print output 
+0

@andrew +1, muchas gracias por responder – newbe

0

escribí this tool hace un tiempo, se puede actualmente co pe con listas anidadas, dictados y conjuntos. le da una salida más concisa (el . en . > i:1 > 'c' se refiere al nivel superior y el i:1 se refiere al índice 1 de la lista que se comparan):

compare(ldA, ldB) 
. > i:0 > 'b' dict value is different: 
100.0 
99.9 

. > i:0 > 'd' dict value is different: 
48.9 
43.7 

. > i:1 > 'a' dict value is different: 
46.7 
67.7 

. > i:1 > 'c' dict value is different: 
0.0 
1.1 

. lists differed at positions: 2 
['<not present>'] 
[{'c': 2.2, 'd': 6.5, 'a': 89.9, 'user': 'nameC', 'b': 77.3}] 
Cuestiones relacionadas