web-dev-qa-db-fra.com

Comment comparer efficacement deux listes non ordonnées (et non des ensembles) en Python?

a = [1, 2, 3, 1, 2, 3]
b = [3, 2, 1, 3, 2, 1]

a & b doivent être considérés comme égaux, car ils ont exactement les mêmes éléments, mais dans un ordre différent.

Le fait est que mes listes réelles seront constituées d'objets (mes instances de classe), et non de nombres entiers.

119
johndir

O (n) : La méthode Counter () est la meilleure (si vos objets sont lavables):

def compare(s, t):
    return Counter(s) == Counter(t)

O (n log n) : La méthode sort (=) est la meilleure (= si vos objets sont commandables):

def compare(s, t):
    return sorted(s) == sorted(t)

O (n * n) : Si les objets ne sont ni haschables, ni ordonnables, vous pouvez utiliser l'égalité:

def compare(s, t):
    t = list(t)   # make a mutable copy
    try:
        for elem in s:
            t.remove(elem)
    except ValueError:
        return False
    return not t
205
Raymond Hettinger

Vous pouvez trier les deux:

sorted(a) == sorted(b)

Un sorte de comptage pourrait aussi être plus efficace (mais cela nécessite que l'objet soit hashable).

>>> from collections import Counter
>>> a = [1, 2, 3, 1, 2, 3]
>>> b = [3, 2, 1, 3, 2, 1]
>>> print (Counter(a) == Counter(b))
True
14
Mark Byers

Si vous savez que les éléments sont toujours obligatoires, vous pouvez utiliser une Counter() qui est O (n).
Si vous savez que les éléments sont toujours triables, vous pouvez utiliser sorted() qui est O (n log n)

Dans le cas général, vous ne pouvez pas compter sur le tri, ni sur les éléments, vous avez donc besoin d’un repli comme celui-ci, qui est malheureusement O (n ^ 2)

len(a)==len(b) and all(a.count(i)==b.count(i) for i in a)
11
John La Rooy

La meilleure façon de faire est de trier les listes et de les comparer. (Utiliser Counter ne fonctionnera pas avec des objets qui ne sont pas haschables.) C'est simple pour les entiers:

sorted(a) == sorted(b)

Cela devient un peu plus délicat avec des objets arbitraires. Si vous vous souciez de l'identité de l'objet, c'est-à-dire si les objets identiques sont dans les deux listes, vous pouvez utiliser la fonction id() comme clé de tri.

sorted(a, key=id) == sorted(b, key==id)

(Dans Python 2.x, vous n'avez pas réellement besoin du paramètre key=, Car vous pouvez comparer n'importe quel objet à n'importe quel objet. L'ordre est arbitraire mais stable, donc cela fonctionne bien pour cela; l'ordre dans lequel les objets se trouvent n'a pas d'importance, il suffit que l'ordre soit le même pour les deux listes. Dans Python 3, toutefois, la comparaison d'objets de types différents est interdite dans de nombreuses circonstances - par exemple, vous pouvez Ne comparez pas les chaînes à des entiers - si vous avez des objets de différents types, mieux vaut utiliser explicitement l'ID de l'objet.)

Si vous voulez comparer les objets de la liste avec valeur, , vous devez d’abord définir ce que "valeur" signifie pour les objets. Ensuite, vous aurez besoin d'un moyen de fournir cela en tant que clé (et pour Python 3 en tant que type cohérent). Une méthode potentielle pour de nombreux objets arbitraires consiste à trier leurs repr(). Bien sûr, cela pourrait perdre beaucoup de temps et de mémoire en construction repr() chaînes pour les grandes listes, etc.

sorted(a, key=repr) == sorted(b, key==repr)

Si les objets sont tous vos propres types, vous pouvez définir __lt__() sur eux afin que l'objet sache se comparer aux autres. Ensuite, vous pouvez simplement les trier sans vous soucier du paramètre key=. Bien sûr, vous pouvez également définir __hash__() et utiliser Counter, ce qui sera plus rapide.

5
kindall

Si la liste contient des éléments qui ne sont pas haschables (comme une liste d'objets), vous pourrez peut-être utiliser la fonction Counter Class et la fonction id (), par exemple:

from collections import Counter
...
if Counter(map(id,a)) == Counter(map(id,b)):
    print("Lists a and b contain the same objects")
3
Mars

Si la comparaison doit être effectuée dans un contexte de test, utilisez assertCountEqual(a, b) (py>=3.2) Et assertItemsEqual(a, b) (2.7<=py<3.2).

Fonctionne également sur des séquences d'objets insaisissables.

2
jarekwg

https://docs.python.org/3.5/library/unittest.html#unittest.TestCase.assertCountEqual

assertCountEqual (premier, deuxième, msg = aucun)

Tester que la séquence contient d’abord les mêmes éléments que la seconde, quel que soit leur ordre. Si ce n’est pas le cas, un message d’erreur énumérant les différences entre les séquences sera généré.

Les éléments en double ne sont pas ignorés lors de la comparaison des premier et second. Il vérifie si chaque élément a le même compte dans les deux séquences. Équivalent à: assertEqual (Counter (liste (première)), Counter (liste (seconde))), mais fonctionne également avec des séquences d'objets inutiles.

Nouveau dans la version 3.2.

ou dans 2.7: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual

2
cleder

Laissez a, b listes

def ass_equal(a,b):
try:
    map(lambda x: a.pop(a.index(x)), b) # try to remove all the elements of b from a, on fail, throw exception
    if len(a) == 0: # if a is empty, means that b has removed them all
        return True 
except:
    return False # b failed to remove some items from a

Pas besoin de les rendre hashable ou de les trier.

1
Umur Kontacı

J'espère que le code ci-dessous fonctionnera dans votre cas: -

if ((len(a) == len(b)) and
   (all(i in a for i in b))):
    print 'True'
else:
    print 'False'

Cela garantira que tous les éléments des deux listes a & b sont identiques, qu'ils soient ou non dans le même ordre.

Pour une meilleure compréhension, reportez-vous à ma réponse dans cette question

1
Pabitra Pati