Python, différence de liste de calcul


195

En Python, quelle est la meilleure façon de calculer la différence entre deux listes?

exemple

A = [1,2,3,4]
B = [2,5]

A - B = [1,3,4]
B - A = [5]

Réponses:


206

Utilisez setsi vous ne vous souciez pas de la commande ou de la répétition des articles. Utilisez des listes de compréhension si vous le faites:

>>> def diff(first, second):
        second = set(second)
        return [item for item in first if item not in second]

>>> diff(A, B)
[1, 3, 4]
>>> diff(B, A)
[5]
>>> 

31
Envisagez d'utiliser set(b)pour vous assurer que l'algorithme est O (nlogn) au lieu de Theta (n ^ 2)
Neil G

8
@Pencilcheck - pas si vous vous souciez de la commande ou de la duplication dans A. Appliquer setà B est inoffensif, mais l'appliquer à Aet utiliser le résultat au lieu de l'original Ane l'est pas.
Mark Reed

1
@NeilG Considérez-vous le temps consacré à la construction de l'ensemble? Dans mon cas (les deux listes ont environ 10M de chaînes), le temps de construire deux ensembles et de les soustraire est considérablement plus important que de créer un ensemble et d'itérer sur la liste.
dimril

@dimril si c'est ce que vous voulez faire, vous devriez peut-être implémenter quelque chose de plus sophistiqué. Vous pouvez par exemple trier les deux listes O (n log n + m log m) puis parcourir la deuxième liste mais utiliser la recherche binaire pour trouver les éléments de la première liste. Cela reviendrait aux opérations O (n log n + m log m + m log n) (au lieu des opérations O (n * m)), ce qui ne semble pas trop mal. Assurez-vous simplement de vérifier les voisins pour éliminer également les doublons dans vos implémentations de recherche binaire. Il pourrait même y avoir un package qui implémente déjà cela, mais je n'ai pas vérifié.
jaaq

366

Si la commande n'a pas d'importance, vous pouvez simplement calculer la différence définie:

>>> set([1,2,3,4]) - set([2,5])
set([1, 4, 3])
>>> set([2,5]) - set([1,2,3,4])
set([5])

9
C'est de loin la meilleure solution. Le cas de test sur des listes de ~ 6000 chaînes chacune a montré que cette méthode était presque 100 fois plus rapide que les compréhensions de liste.
perrygeo

15
Dépend de l'application: si la préservation de l'ordre ou de la duplication est importante, Roman Bodnarchuk peut avoir une meilleure approche. Pour la vitesse et le comportement purement défini, celui-ci semble meilleur.
Bryan P

7
Si vous avez plusieurs éléments égaux dans la liste, cette solution ne fonctionnera pas.
karantan

Bien mieux que la compréhension des listes.
Dawei

4
Cette solution semble si évidente mais elle est incorrecte. Je suis désolé. Bien sûr, nous voulons dire qu'une liste peut avoir des éléments égaux répétés. Sinon, nous demandons la différence entre les ensembles, pas la différence de liste.
sergzach

67

Vous pouvez faire un

list(set(A)-set(B))

et

list(set(B)-set(A))

7
Mais si A = [1,1,1] et B = [0] alors cela renvoie [1]
Mark Bell

1
@Mark Bell: C'est parce qu'un ensemble est une liste distincte. (supprime les doublons)
nuageux

1
@cloudy Alors cela ne répond pas à la question.
samm82

@ samm82 si A = [1,1,1] que set (A) est [1] car set est une liste distincte et supprime les doublons. C'est pourquoi, si A = [1,1,1] et B = [0], il renvoie [1].
nuageux

29

Bon mot:

diff = lambda l1,l2: [x for x in l1 if x not in l2]
diff(A,B)
diff(B,A)

Ou:

diff = lambda l1,l2: filter(lambda x: x not in l2, l1)
diff(A,B)
diff(B,A)

14

Les exemples ci-dessus ont banalisé le problème du calcul des différences. En supposant que le tri ou la déduplication facilite certainement le calcul de la différence, mais si votre comparaison ne peut pas se permettre ces hypothèses, vous aurez besoin d'une implémentation non triviale d'un algorithme diff. Voir difflib dans la bibliothèque standard de python.

from difflib import SequenceMatcher 

squeeze=SequenceMatcher( None, A, B )

print "A - B = [%s]"%( reduce( lambda p,q: p+q, 
                               map( lambda t: squeeze.a[t[1]:t[2]], 
                                    filter(lambda x:x[0]!='equal', 
                                           squeeze.get_opcodes() ) ) ) )

A - B = [[1, 3, 4]]


1
vous obtenez +1 pour difflib, que je n'avais jamais vu auparavant. néanmoins, je ne suis pas d'accord que les réponses ci-dessus banalisent le problème comme indiqué .
rbp

Merci d'utiliser difflib - je cherchais une solution en utilisant la bibliothèque standard. Cependant, cela ne fonctionne pas dans Python 3, comme cela printest passé d'une commande à une fonction, et reduce, filteret mapont été déclarés non pythoniques. (Et je pense que Guido a peut-être raison - je ne comprends pas reducenon plus.)
Post169

Pas un gros changement pour le faire fonctionner pour py3. J'ai lu le débat sur le filtre, la carte, la réduction et je suis d'accord avec le choix de pousser la réduction et l'alternance du filtre dans functools. La nature mixte fonctionnelle, OO et procédurale du python a toujours été, l'OMI, l'une de ses forces.
Kevin

14

Python 2.7.3 (par défaut, 27 février 2014, 19:58:35) - IPython 1.1.0 - timeit: (github gist)

def diff(a, b):
  b = set(b)
  return [aa for aa in a if aa not in b]

def set_diff(a, b):
  return list(set(a) - set(b))

diff_lamb_hension = lambda l1,l2: [x for x in l1 if x not in l2]

diff_lamb_filter = lambda l1,l2: filter(lambda x: x not in l2, l1)

from difflib import SequenceMatcher
def squeezer(a, b):
  squeeze = SequenceMatcher(None, a, b)
  return reduce(lambda p,q: p+q, map(
    lambda t: squeeze.a[t[1]:t[2]],
      filter(lambda x:x[0]!='equal',
        squeeze.get_opcodes())))

Résultats:

# Small
a = range(10)
b = range(10/2)

timeit[diff(a, b)]
100000 loops, best of 3: 1.97 µs per loop

timeit[set_diff(a, b)]
100000 loops, best of 3: 2.71 µs per loop

timeit[diff_lamb_hension(a, b)]
100000 loops, best of 3: 2.1 µs per loop

timeit[diff_lamb_filter(a, b)]
100000 loops, best of 3: 3.58 µs per loop

timeit[squeezer(a, b)]
10000 loops, best of 3: 36 µs per loop

# Medium
a = range(10**4)
b = range(10**4/2)

timeit[diff(a, b)]
1000 loops, best of 3: 1.17 ms per loop

timeit[set_diff(a, b)]
1000 loops, best of 3: 1.27 ms per loop

timeit[diff_lamb_hension(a, b)]
1 loops, best of 3: 736 ms per loop

timeit[diff_lamb_filter(a, b)]
1 loops, best of 3: 732 ms per loop

timeit[squeezer(a, b)]
100 loops, best of 3: 12.8 ms per loop

# Big
a = xrange(10**7)
b = xrange(10**7/2)

timeit[diff(a, b)]
1 loops, best of 3: 1.74 s per loop

timeit[set_diff(a, b)]
1 loops, best of 3: 2.57 s per loop

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# TypeError: sequence index must be integer, not 'slice'

@ rom-bodnarchuk list comprehensions function def diff (a, b) semble être plus rapide.


9
A = [1,2,3,4]
B = [2,5]

#A - B
x = list(set(A) - set(B))
#B - A 
y = list(set(B) - set(A))

print x
print y 


5

Dans le cas où vous souhaitez que la différence rentre profondément dans les éléments de votre liste, j'ai écrit un package pour python: https://github.com/erasmose/deepdiff

Installation

Installer depuis PyPi:

pip install deepdiff

Si vous êtes Python3, vous devez également installer:

pip install future six

Exemple d'utilisation

>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function

Le même objet revient vide

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {}

Le type d'un article a changé

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'type_changes': ["root[2]: 2=<type 'int'> vs. 2=<type 'str'>"]}

La valeur d'un article a changé

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'values_changed': ['root[2]: 2 ====>> 4']}

Élément ajouté et / ou supprimé

>>> t1 = {1:1, 2:2, 3:3, 4:4}
>>> t2 = {1:1, 2:4, 3:3, 5:5, 6:6}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes)
    {'dic_item_added': ['root[5, 6]'],
     'dic_item_removed': ['root[4]'],
     'values_changed': ['root[2]: 2 ====>> 4']}

Différence de chaîne

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world"}}
>>> t2 = {1:1, 2:4, 3:3, 4:{"a":"hello", "b":"world!"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ 'root[2]: 2 ====>> 4',
                          "root[4]['b']:\n--- \n+++ \n@@ -1 +1 @@\n-world\n+world!"]}
>>>
>>> print (ddiff.changes['values_changed'][1])
    root[4]['b']:
    --- 
    +++ 
    @@ -1 +1 @@
    -world
    +world!

Différence de chaîne 2

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world!\nGoodbye!\n1\n2\nEnd"}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n1\n2\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ "root[4]['b']:\n--- \n+++ \n@@ -1,5 +1,4 @@\n-world!\n-Goodbye!\n+world\n 1\n 2\n End"]}
>>>
>>> print (ddiff.changes['values_changed'][0])
    root[4]['b']:
    --- 
    +++ 
    @@ -1,5 +1,4 @@
    -world!
    -Goodbye!
    +world
     1
     2
     End

Changement de type

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n\n\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'type_changes': [ "root[4]['b']: [1, 2, 3]=<type 'list'> vs. world\n\n\nEnd=<type 'str'>"]}

Liste des différences

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'list_removed': ["root[4]['b']: [3]"]}

Énumérez la différence 2: notez qu'elle ne tient pas compte de l'ordre

>>> # Note that it DOES NOT take order into account
... t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { }

Liste contenant le dictionnaire:

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:1, 2:2}]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:3}]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'dic_item_removed': ["root[4]['b'][2][2]"],
      'values_changed': ["root[4]['b'][2][1]: 1 ====>> 3"]}

5

moyen le plus simple,

utilisez set (). difference (set ())

list_a = [1,2,3]
list_b = [2,3]
print set(list_a).difference(set(list_b))

la réponse est set([1])


2

Dans le cas d'une liste de dictionnaires , la solution de compréhension de liste complète fonctionne pendant que la setsolution soulève

TypeError: unhashable type: 'dict'

Cas de test

def diff(a, b):
    return [aa for aa in a if aa not in b]

d1 = {"a":1, "b":1}
d2 = {"a":2, "b":2}
d3 = {"a":3, "b":3}

>>> diff([d1, d2, d3], [d2, d3])
[{'a': 1, 'b': 1}]
>>> diff([d1, d2, d3], [d1])
[{'a': 2, 'b': 2}, {'a': 3, 'b': 3}]

0

Code simple qui vous donne la différence avec plusieurs éléments si vous le souhaitez:

a=[1,2,3,3,4]
b=[2,4]
tmp = copy.deepcopy(a)
for k in b:
    if k in tmp:
        tmp.remove(k)
print(tmp)

-1

Lorsque l'on regarde TimeComplexity of In-operator, dans le pire des cas, cela fonctionne avec O (n). Même pour les ensembles.

Ainsi, lorsque nous comparons deux tableaux, nous aurons une TimeComplexity de O (n) dans le meilleur des cas et O (n ^ 2) dans le pire des cas.

Une solution alternative (mais malheureusement plus complexe), qui fonctionne avec O (n) dans le meilleur et le pire des cas est celle-ci:

# Compares the difference of list a and b
# uses a callback function to compare items
def diff(a, b, callback):
  a_missing_in_b = []
  ai = 0
  bi = 0

  a = sorted(a, callback)
  b = sorted(b, callback)

  while (ai < len(a)) and (bi < len(b)):

    cmp = callback(a[ai], b[bi])
    if cmp < 0:
      a_missing_in_b.append(a[ai])
      ai += 1
    elif cmp > 0:
      # Item b is missing in a
      bi += 1
    else:
      # a and b intersecting on this item
      ai += 1
      bi += 1

  # if a and b are not of same length, we need to add the remaining items
  for ai in xrange(ai, len(a)):
    a_missing_in_b.append(a[ai])


  return a_missing_in_b

par exemple

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.