web-dev-qa-db-fra.com

Déterminer si 2 listes ont les mêmes éléments, quel que soit leur ordre?

Désolé pour la question simple, mais j'ai du mal à trouver la réponse.

Quand je compare 2 listes, je veux savoir si elles sont "égales" en ce sens qu'elles ont le même contenu mais dans un ordre différent.

Ex:

x = ['a', 'b']
y = ['b', 'a']

Je veux que x == y soit évalué à True.

84
toofly

Vous pouvez simplement vérifier si les multisets avec les éléments de x et y sont égaux:

import collections
collections.Counter(x) == collections.Counter(y)

Cela nécessite que les éléments soient lavables; le runtime sera dans O(n), où n est la taille des listes.

Si les éléments sont également uniques, vous pouvez également convertir en ensembles (même exécution asymptotique, peut-être un peu plus rapide en pratique):

set(x) == set(y)

Si les éléments ne sont pas haschables, mais peuvent être triés, une autre alternative (runtime dans O(n log n)) est

sorted(x) == sorted(y)

Si les éléments ne sont ni triables, ni triables, vous pouvez utiliser la fonction d'assistance suivante. Notez qu'il sera assez lent (O(n²)) et devrait généralement être utilisé pas / en dehors du cas ésotérique d’éléments inshases et insortables.

def equal_ignore_order(a, b):
    """ Use only when elements are neither hashable nor sortable! """
    unmatched = list(b)
    for element in a:
        try:
            unmatched.remove(element)
        except ValueError:
            return False
    return not unmatched
124
phihag

Déterminez si 2 listes ont les mêmes éléments, quel que soit leur ordre?

Déduire de votre exemple:

x = ['a', 'b']
y = ['b', 'a']

que les éléments des listes ne seront pas répétés (ils sont uniques) ainsi que haschables (quelles chaînes et autres objets python immuables sont), la réponse la plus directe et la plus efficace en termes de calcul(qui sont sémantiquement comme des ensembles mathématiques que vous avez peut-être appris à l'école). 

set(x) == set(y) # prefer this if elements are hashable

Dans le cas où les éléments sont hashable, mais non uniques, le collections.Counter fonctionne aussi sémantiquement comme un multiset, mais il est beaucoup plus lent:

from collections import Counter
Counter(x) == Counter(y)

Préfère utiliser sorted:

sorted(x) == sorted(y) 

si les éléments sont commandables. Cela expliquerait des circonstances non uniques ou non-utilisables, mais cela pourrait être beaucoup plus lent que l'utilisation d'ensembles.

Expérience empirique

Une expérience empirique conclut que l’on devrait préférer set, puis sorted. N'optez que pour Counter si vous avez besoin d'autres éléments tels que des comptes ou une utilisation ultérieure en tant que multiset.

Première installation:

import timeit
import random
from collections import Counter

data = [str(random.randint(0, 100000)) for i in xrange(100)]
data2 = data[:]     # copy the list into a new one

def sets_equal(): 
    return set(data) == set(data2)

def counters_equal(): 
    return Counter(data) == Counter(data2)

def sorted_lists_equal(): 
    return sorted(data) == sorted(data2)

Et tester:

>>> min(timeit.repeat(sets_equal))
13.976069927215576
>>> min(timeit.repeat(counters_equal))
73.17287588119507
>>> min(timeit.repeat(sorted_lists_equal))
36.177085876464844

Nous voyons donc que comparer des ensembles est la solution la plus rapide, et comparer des listes triées est la deuxième plus rapide.

13
Aaron Hall

Cela semble fonctionner, bien que éventuellement lourd pour les grandes listes.

>>> A = [0, 1]
>>> B = [1, 0]
>>> C = [0, 2]
>>> not sum([not i in A for i in B])
True
>>> not sum([not i in A for i in C])
False
>>> 

Cependant, si chaque liste doit contient tous les éléments d’autre, le code ci-dessus est problématique. 

>>> A = [0, 1, 2]
>>> not sum([not i in A for i in B])
True

Le problème survient lorsque len(A) != len(B) et, dans cet exemple, len(A) > len(B). Pour éviter cela, vous pouvez ajouter une déclaration supplémentaire.

>>> not sum([not i in A for i in B]) if len(A) == len(B) else False
False

Une dernière chose, j’ai comparé ma solution avec timeit.repeat, dans les mêmes conditions que celles utilisées par Aaron Hall dans ses fonctions. Comme suspecté, les résultats sont décevants. Ma méthode est la dernière. set(x) == set(y) il l'est.

>>> def foocomprehend(): return not sum([not i in data for i in data2])
>>> min(timeit.repeat('fooset()', 'from __main__ import fooset, foocount, foocomprehend'))
25.2893661496
>>> min(timeit.repeat('foosort()', 'from __main__ import fooset, foocount, foocomprehend'))
94.3974742993
>>> min(timeit.repeat('foocomprehend()', 'from __main__ import fooset, foocount, foocomprehend'))
187.224562545
1
blahreport

Comme mentionné dans les commentaires ci-dessus, le cas général est pénible. C’est assez facile si tous les articles peuvent être triés ou peuvent être triés. Cependant, j'ai récemment eu à essayer de résoudre le cas général. Voici ma solution. J'ai réalisé après la publication que c'était une copie de la solution ci-dessus que j'avais manquée lors du premier passage. Quoi qu'il en soit, si vous utilisez des tranches plutôt que list.remove (), vous pouvez comparer des séquences immuables.

def sequences_contain_same_items(a, b):
    for item in a:
        try:
            i = b.index(item)
        except ValueError:
            return False
        b = b[:i] + b[i+1:]
    return not b
0
Grahame