182 votes

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

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

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

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

7 votes

Comment les objets sont-ils comparés ?

2 votes

Quelle est la taille attendue des listes réelles ? Les listes comparées seront-elles de tailles comparables ou très différentes ? Vous attendez-vous à ce que la plupart des listes correspondent ou non ?

0 votes

On peut vérifier len() és en premier.

303voto

Raymond Hettinger Points 50330

O(n) : Le Compteur() est la meilleure méthode (si vos objets sont hachables) :

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

O(n log n) : Le trié() est la meilleure solution (si vos objets peuvent être commandés) :

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

O(n * n) : Si les objets ne sont ni hachables, 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

1 votes

Nous vous remercions. J'ai converti chaque objet en chaîne de caractères, puis j'ai utilisé la méthode Counter().

0 votes

Hey @Raymond, j'ai récemment rencontré cette question lors d'un entretien et j'ai utilisé sorted() Il est vrai qu'il ne connaissait pas l'existence de la Counter . L'interviewer a insisté sur le fait qu'il existait une méthode plus efficace et j'ai manifestement fait chou blanc. Après des tests approfondis en python 3 avec l'outil timeit le module trié est toujours plus rapide sur les listes d'entiers. Sur des listes de 1 000 éléments, il est environ 1,5 % plus lent et sur des listes courtes de 10 éléments, il est 7,5 % plus lent. Qu'en pensez-vous ?

4 votes

Pour les listes courtes, l'analyse big-O n'est généralement pas pertinente car les délais sont dominés par des facteurs constants. Pour les listes plus longues, je soupçonne que quelque chose ne va pas dans votre analyse comparative. Pour 100 ints avec 5 répétitions chacun, j'obtiens 127 usec pour sorted et 42 pour Counter (environ 3x plus rapide) : 127 usec pour sorted et 42 pour Counter (environ 3x plus rapide). Pour 1 000 ints avec 5 répétitions, Counter est 4 fois plus rapide. python3.6 -m timeit -s 'from collections import Counter' -s 'from random import shuffle' -s 't=list(range(100)) * 5' -s 'shuffle(t)' -s 'u=t[:]' -s 'shuffle(u)' 'Counter(t)==Counter(u)'

20voto

Mark Byers Points 318575

Vous pouvez trier les deux :

sorted(a) == sorted(b)

A tri par comptage pourrait également être plus efficace (mais elle nécessite que l'objet soit hachable).

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

0 votes

Le compteur utilise le hachage, mais les objets ne sont pas non hachables en soi. Il suffit d'implémenter un __hash__ mais cela peut s'avérer impossible pour les collections.

2 votes

Le tri ne fonctionne pas non plus pour tout, par exemple pour les nombres complexes. sorted([0, 1j])

1 votes

Sorted() ne fonctionne pas non plus avec les ensembles dont les opérateurs de comparaison ont été remplacés pour les tests de sous-ensembles/supersets.

13voto

gnibbler Points 103484

Si vous savez que les éléments sont toujours hachables, vous pouvez utiliser une fonction Counter() qui est O(n)
Si vous savez que les éléments peuvent toujours être triés, vous pouvez utiliser sorted() ce qui est O(n log n)

Dans le cas général, vous ne pouvez pas compter sur la possibilité de trier ou de disposer des éléments, vous avez donc besoin d'une solution de rechange comme celle-ci, qui est malheureusement O(n^2)

len(a)==len(b) and all(a.count(i)==b.count(i) for i in a)

6voto

kindall Points 60645

La meilleure façon de procéder consiste à trier les listes et à les comparer. (En utilisant Counter ne fonctionnera pas avec des objets qui ne sont pas hachables). C'est simple pour les entiers :

sorted(a) == sorted(b)

La situation est un peu plus délicate avec les objets arbitraires. Si vous vous souciez de l'identité de l'objet, c'est-à-dire de savoir si l'objet même se trouvent 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 besoin de l'option key= car on peut comparer n'importe quel objet à n'importe quel objet. L'ordre est arbitraire mais stable, et fonctionne donc très bien pour cet objectif ; l'ordre des objets n'a pas d'importance, il suffit que l'ordre soit le même pour les deux listes. Dans Python 3, cependant, la comparaison d'objets de types différents est interdite dans de nombreuses circonstances - par exemple, vous ne pouvez pas comparer des chaînes de caractères à des entiers - donc si vous avez des objets de types différents, il vaut mieux utiliser explicitement l'ID de l'objet).

Si vous souhaitez comparer les objets de la liste par valeur, d'autre part, il faut d'abord définir ce que signifie la "valeur" pour les objets. Il faut ensuite trouver un moyen de fournir cette valeur comme clé (et pour Python 3, comme type cohérent). Un moyen potentiel qui fonctionnerait pour un grand nombre d'objets arbitraires est de trier par leur repr() . Bien sûr, cela pourrait faire perdre beaucoup de temps et de mémoire à la construction de l'ordinateur repr() pour les grandes listes, etc.

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

Si les objets sont tous de votre propre type, vous pouvez définir __lt__() afin que l'objet sache se comparer aux autres. Ensuite, vous pouvez simplement les trier sans vous préoccuper de l'ordre des objets. key= paramètre. Bien entendu, vous pouvez également définir __hash__() et utiliser Counter qui sera plus rapide.

1voto

Umur Kontacı Points 12524

Listes a, b

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

Il n'est pas nécessaire de les rendre hachables ou de les trier.

1 votes

Oui, mais cette méthode est O(n**2), comme l'ont indiqué plusieurs autres affiches, et ne devrait donc être utilisée que si les autres méthodes ne fonctionnent pas. Elle suppose également que a soutiens pop (est mutable) et index (est une séquence). Celle de Raymond ne suppose ni l'une ni l'autre, tandis que celle de gnibbler ne suppose qu'une séquence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X