Jak efektywnie porównać dwie nieuporządkowane listy (nie zestawy) w Pythonie?


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

a i b należy uważać za równe, ponieważ mają dokładnie te same elementy, tylko w różnej kolejności.

Rzecz w tym, że moje rzeczywiste listy będą składać się z obiektów (instancji moich klas), a nie liczb całkowitych.


7
Jak porównuje się obiekty?
— Marcelo Cantos

2
jaki jest oczekiwany rozmiar prawdziwych list? Czy porównywane listy będą miały porównywalne rozmiary, czy też będą bardzo różne? Czy spodziewasz się, że większość list będzie pasować, czy nie?
— Dmitry B.,

Można len()najpierw sprawdzić .
— siwobrody

Odpowiedzi:


245

O (n) : Metoda Counter () jest najlepsza (jeśli twoje obiekty są haszowane):

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

O (n log n) : Metoda sortowana () jest następna w kolejności (jeśli obiekty można uporządkować):

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

O (n * n) : Jeśli obiekty nie są ani hashowalne, ani uporządkowane, możesz użyć równości:

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
Dziękuję Ci. Przekonwertowałem każdy obiekt na łańcuch, a następnie użyłem metody Counter ().
— johndir

Hej @Raymond, niedawno spotkałem się z tym pytaniem w wywiadzie i użyłem sorted(), wprawdzie nie wiedząc o tym Counter. Ankieter nalegał, że jest bardziej skuteczna metoda i najwyraźniej wyciągnąłem lukę. Po obszernych testach w Pythonie 3 z timeitmodułem posortowane konsekwentnie pojawiają się szybciej na listach liczb całkowitych. Na listach 1k pozycji, około 1,5% wolniej, a na krótkich listach 10 pozycji, 7,5% wolniej. Myśli?
— arctelix

4
W przypadku krótkich list, analiza big-O jest zwykle nieistotna, ponieważ czasy są zdominowane przez stałe czynniki. W przypadku dłuższych list podejrzewam, że coś jest nie tak z twoim benchmarkingiem. Dla 100 int z 5 powtórzeniami każdy otrzymuję: 127 usek dla sortowania i 42 dla Counter (około 3x szybciej). Przy 1000 int z 5 powtórzeniami Counter jest 4x szybszy. 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)'
— Raymond Hettinger

@Raymond Rzeczywiście otrzymujemy różne wyniki. Wysłałem moją konfigurację do pokoju rozmów sorted vs counter. Jestem bardzo ciekawy, co się tutaj dzieje.
— arctelix

4
Nie, dziękuję. Nie interesuje mnie debugowanie fałszywych skryptów czasu. Wiele się tu dzieje (czysty kod Python kontra C, sortowanie czasu stosowane do danych losowych w porównaniu z danymi częściowo uporządkowanymi, różne szczegóły implementacji w różnych wersjach, liczba duplikatów danych itp.)
— Raymond Hettinger

16

Możesz sortować zarówno:

sorted(a) == sorted(b)

Sortowanie przez zliczanie może być również bardziej wydajny (ale wymaga obiektu przeznaczonego do 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

Licznik używa hashowania, ale obiekty nie są nierozłączne jako takie. Wystarczy wdrożyć rozsądne __hash__, ale w przypadku kolekcji może to być niemożliwe.
— Jochen Ritzel

2
posortowane też nie zadziałają na wszystko, np. liczby zespolonesorted([0, 1j])
— John La Rooy,

1
sortowana () również nie działa z zestawami, w których operatory porównania zostały nadpisane w testach podzbiorów / superzbiorów.
— Raymond Hettinger

12

Jeśli wiesz, że elementy są zawsze haszowane, możesz użyć a, Counter()które jest O (n)
Jeśli wiesz, że elementy są zawsze możliwe do sortowania, możesz użyć, sorted()które jest O (n log n)

W ogólnym przypadku nie możesz polegać na możliwości sortowania lub na posiadaniu elementów, więc potrzebujesz takiego rozwiązania awaryjnego, które niestety jest O (n ^ 2)

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

5

Najlepszym sposobem na to jest sortowanie list i porównywanie ich. (Użycie Counternie zadziała z obiektami, które nie są hashowane). Jest to proste w przypadku liczb całkowitych:

sorted(a) == sorted(b)

Trochę trudniej jest z przypadkowymi obiektami. Jeśli zależy Ci na tożsamości obiektu, tj. Czy te same obiekty znajdują się na obu listach, możesz użyć id()funkcji jako klucza sortowania.

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

(W Pythonie 2.x nie potrzebujesz tego key=parametru, ponieważ możesz porównać dowolny obiekt z dowolnym obiektem. Kolejność jest dowolna, ale stabilna, więc działa dobrze w tym celu; nie ma znaczenia, w jakiej kolejności są obiekty in, tylko że kolejność jest taka sama dla obu list. Jednak w Pythonie 3 porównywanie obiektów różnych typów jest w wielu przypadkach niedozwolone - na przykład nie możesz porównywać łańcuchów z liczbami całkowitymi - więc jeśli będziesz mieć obiekty różnych typów, najlepiej jawnie użyć identyfikatora obiektu).

Z drugiej strony, jeśli chcesz porównać obiekty na liście według wartości , najpierw musisz zdefiniować, co oznacza „wartość” dla obiektów. Wtedy będziesz potrzebował jakiegoś sposobu, aby podać go jako klucz (a dla Pythona 3 jako spójny typ). Jednym z potencjalnych sposobów działania w przypadku wielu dowolnych obiektów jest sortowanie według ich repr(). Oczywiście może to zmarnować dużo dodatkowego czasu i repr()ciągów budujących pamięć dla dużych list i tak dalej.

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

Jeśli wszystkie obiekty są twoimi własnymi typami, możesz __lt__()na nich zdefiniować, aby obiekt wiedział, jak porównywać się z innymi. Następnie możesz je po prostu posortować i nie martwić się o key=parametr. Oczywiście możesz także zdefiniować __hash__()i użyć Counter, co będzie szybsze.


4

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

assertCountEqual (pierwszy, drugi, msg = brak)

Przetestuj, że sekwencja najpierw zawiera te same elementy, co druga, niezależnie od ich kolejności. Jeśli tak nie jest, zostanie wygenerowany komunikat o błędzie zawierający różnice między sekwencjami.

Powielone elementy nie są ignorowane podczas porównywania pierwszego i drugiego. Sprawdza, czy każdy element ma taką samą liczbę w obu sekwencjach. Równoważne z: assertEqual (Counter (lista (pierwsza)), Counter (lista (druga))), ale działa również z sekwencjami obiektów, których nie można zhashować.

Nowość w wersji 3.2.

lub w wersji 2.7: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual


2
(Co to dodaje do odpowiedzi Jarekwga ?)
— Greybeard

3

Jeśli lista zawiera elementy, których nie można haszować (na przykład listę obiektów), możesz użyć klasy Counter i funkcji id (), takiej jak:

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

2

Mam nadzieję, że poniższy fragment kodu może działać w twoim przypadku: -

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

Zapewni to, że wszystkie elementy na obu listach aib są takie same, niezależnie od tego, czy są one w tej samej kolejności, czy nie.

Dla lepszego zrozumienia zapoznaj się z moją odpowiedzią na to pytanie



1

Niech a, b wymienia

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

Nie ma potrzeby ich haszowania ani sortowania.


1
Tak, ale to jest O (n ** 2), jak wskazało kilka innych plakatów, więc powinno być używane tylko wtedy, gdy inne metody nie działają. Zakłada również, że aobsługuje pop(jest zmienny) i index(jest sekwencją). Raymond's nie zakłada żadnego, podczas gdy Gnibbler zakłada tylko sekwencję.
— agf

0

Korzystanie z unittestmodułu zapewnia przejrzyste i standardowe podejście.

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.