Mam listę list w Pythonie:
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
I chcę usunąć z niego zduplikowane elementy. To była zwykła lista, której nie mógłbym użyć set
. Niestety, ta lista nie jest haszowalna i nie może tworzyć zestawu list. Tylko krotek. Mogę więc zmienić wszystkie listy w krotki, a następnie użyć set i z powrotem do list. Ale to nie jest szybkie.
Jak można to zrobić w najbardziej efektywny sposób?
Wynik powyższej listy powinien być:
k = [[5, 6, 2], [1, 2], [3], [4]]
Nie obchodzi mnie zachowanie porządku.
Uwaga: to pytanie jest podobne, ale nie do końca to, czego potrzebuję. Przeszukano SO, ale nie znalazłem dokładnego duplikatu.
Benchmarking:
import itertools, time
class Timer(object):
def __init__(self, name=None):
self.name = name
def __enter__(self):
self.tstart = time.time()
def __exit__(self, type, value, traceback):
if self.name:
print '[%s]' % self.name,
print 'Elapsed: %s' % (time.time() - self.tstart)
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000
print len(k)
with Timer('set'):
for i in xrange(N):
kt = [tuple(i) for i in k]
skt = set(kt)
kk = [list(i) for i in skt]
with Timer('sort'):
for i in xrange(N):
ks = sorted(k)
dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
with Timer('groupby'):
for i in xrange(N):
k = sorted(k)
dedup = list(k for k, _ in itertools.groupby(k))
with Timer('loop in'):
for i in xrange(N):
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
"loop in" (metoda kwadratowa) jest najszybszy ze wszystkich dla krótkich list. W przypadku długich list jest szybszy niż wszyscy, z wyjątkiem metody grupowej. Czy to ma sens?
Krótka lista (ta w kodzie), 100000 iteracji:
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
W przypadku dłuższej listy (ta w kodzie powtórzona 5 razy):
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
Odpowiedzi:
itertools
często oferuje najszybsze i najbardziej wydajne rozwiązania tego rodzaju problemów, i jest dobrze warto się zaznajomiony z! -)Edycja : jak wspomniałem w komentarzu, normalne wysiłki optymalizacyjne koncentrują się na dużych nakładach (podejście duże-O), ponieważ jest o wiele łatwiejsze, że oferuje dobre zwroty z wysiłku. Ale czasami (zasadniczo w przypadku „tragicznie kluczowych wąskich gardeł” w głębokich wewnętrznych pętlach kodu, które przesuwają granice limitów wydajności) może zajść potrzeba bardziej szczegółowego omówienia rozkładów prawdopodobieństwa i podjęcia decyzji, które środki wydajności należy zoptymalizować (może górna granica lub 90 centyl jest ważniejszy niż średnia lub mediana, w zależności od aplikacji), przeprowadzając potencjalnie heurystyczne kontrole na początku, aby wybrać różne algorytmy w zależności od charakterystyki danych wejściowych i tak dalej.
Dokładne pomiary wydajności „punktowej” (kod A w porównaniu z kodem B dla konkretnego wejścia) są częścią tego niezwykle kosztownego procesu, a standardowy moduł biblioteki
timeit
pomaga w tym. Jednak łatwiej jest z niego korzystać po znaku zachęty powłoki. Na przykład, oto krótki moduł prezentujący ogólne podejście do tego problemu, zapisz go jakonodup.py
:Zwróć uwagę na kontrolę poczytalności (wykonywaną po prostu
python nodup.py
) i podstawową technikę podnoszenia (uczyń stałe nazwy globalne lokalnymi dla każdej funkcji dla szybkości), aby postawić wszystko na równych zasadach.Teraz możemy przeprowadzić testy na maleńkiej liście przykładów:
potwierdzając, że podejście kwadratowe ma wystarczająco małe stałe, aby uczynić je atrakcyjnym dla małych list z kilkoma zduplikowanymi wartościami. Z krótką listą bez duplikatów:
podejście kwadratowe nie jest złe, ale sortowanie i grupowanie są lepsze. Itd itd.
Jeśli (jak sugeruje obsesja na punkcie wydajności) ta operacja znajduje się w rdzennej wewnętrznej pętli twojej aplikacji przesuwającej granice, warto wypróbować ten sam zestaw testów na innych reprezentatywnych próbkach wejściowych, prawdopodobnie wykrywając jakąś prostą miarę, która może heurystycznie pozwolić wybierz jedno lub drugie podejście (ale środek oczywiście musi być szybki).
Warto również rozważyć zachowanie innej reprezentacji
k
- dlaczego w pierwszej kolejności musi to być lista list, a nie zestaw krotek? Jeśli zadanie usuwania duplikatów jest częste, a profilowanie pokazuje, że jest to wąskie gardło wydajności programu, na przykład utrzymywanie zestawu krotek i uzyskiwanie z niego listy list tylko wtedy, gdy jest to konieczne, może być ogólnie szybsze.źródło
Robiąc to ręcznie, tworząc nową
k
listę i dodając dotychczas nie znalezione wpisy:Proste do zrozumienia i zachowujesz kolejność pierwszego wystąpienia każdego elementu, która powinna być przydatna, ale myślę, że jest to kwadratowe pod względem złożoności, ponieważ szukasz całości
new_k
dla każdego elementu.źródło
k = ([[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] +[[x] for x in range(1000)]) *5
ładnie pokaże kwadratowe zachowanieNie wiem, czy to koniecznie jest szybsze, ale nie musisz używać do krotek i zestawów.
źródło
random
i zmień czastime
.Wszystkie dotychczasowe
set
rozwiązania tego problemu wymagają stworzenia całościset
przed iteracją.Można to uczynić leniwym, a jednocześnie zachować porządek, iterując listę list i dodając do „widziany”
set
. Następnie pokaż listę tylko wtedy, gdy nie zostanie znaleziona w tym trackerzeset
.Ten
unique_everseen
przepis jest dostępny witertools
dokumentacji . Jest również dostępny wtoolz
bibliotece innej firmy :Zwróć uwagę, że
tuple
konwersja jest konieczna, ponieważ listy nie podlegają hashowaniu.źródło
Nawet twoja „długa” lista jest dość krótka. Czy wybrałeś je tak, aby pasowały do rzeczywistych danych? Wydajność będzie się różnić w zależności od tego, jak faktycznie wyglądają te dane. Na przykład, masz krótką listę powtarzaną w kółko, aby utworzyć dłuższą listę. Oznacza to, że rozwiązanie kwadratowe jest liniowe w twoich benchmarkach, ale nie w rzeczywistości.
W przypadku faktycznie dużych list najlepszym rozwiązaniem jest zestaw kod - jest liniowy (chociaż wymaga dużej przestrzeni). Metody sortowania i grupowania to O (n log n), a pętla w metodzie jest oczywiście kwadratowa, więc wiesz, jak będą się one skalować, gdy n stanie się naprawdę duże. Jeśli to jest rzeczywisty rozmiar analizowanych danych, to kogo to obchodzi? Jest malutki.
Nawiasem mówiąc, widzę zauważalne przyspieszenie, jeśli nie utworzę listy pośredniej, aby zrobić zestaw, to znaczy jeśli wymienię
z
Prawdziwe rozwiązanie może zależeć od większej ilości informacji: Czy jesteś pewien, że lista list jest naprawdę reprezentacją, której potrzebujesz?
źródło
Lista krotek i {} może służyć do usuwania duplikatów
źródło
Utwórz słownik z krotką jako kluczem i wydrukuj klucze.
źródło
To powinno działać.
źródło
O dziwo, powyższe odpowiedzi usuwają „duplikaty”, ale co jeśli chcę usunąć również zduplikowaną wartość? Poniższe powinny być przydatne i nie tworzą nowego obiektu w pamięci!
a o / p to:
źródło
Innym, prawdopodobnie bardziej ogólnym i prostszym rozwiązaniem jest utworzenie słownika z kluczem w wersji łańcuchowej obiektów i pobranie na końcu wartości ():
Problem polega na tym, że działa to tylko w przypadku obiektów, których reprezentacja w postaci łańcucha jest wystarczająco dobrym kluczem unikalnym (co jest prawdą dla większości obiektów natywnych).
źródło