Usuwanie duplikatów z list

995

Prawie muszę napisać program, aby sprawdzić, czy lista ma jakieś duplikaty, a jeśli tak, to usuwa je i zwraca nową listę z elementami, które nie zostały zduplikowane / usunięte. To właśnie mam, ale szczerze mówiąc nie wiem, co robić.

def remove_duplicates():
    t = ['a', 'b', 'c', 'd']
    t2 = ['a', 'c', 'd']
    for t in t2:
        t.append(t.remove())
    return t
Neemaximo
źródło
22
Twój opis mówi, że sprawdzasz „listę” w poszukiwaniu duplikatów, ale twój kod sprawdza dwie listy.
Brendan Long
* przy użyciu zestawu: lista (zestaw (ELEMENTS_LIST)) * przy użyciu słownika: lista (dict.fromkeys (ELEMENTS_LIST))
Shayan Amani

Odpowiedzi:

1640

Powszechnym podejściem do uzyskania unikalnej kolekcji przedmiotów jest użycie set. Zestawy to nieuporządkowane kolekcje różnych obiektów. Aby utworzyć zestaw z dowolnej iteracji, możesz po prostu przekazać go do wbudowanej set()funkcji. Jeśli później będziesz potrzebować prawdziwej listy, możesz podobnie przekazać zestaw do list()funkcji.

Poniższy przykład powinien obejmować wszystko, co próbujesz zrobić:

>>> t = [1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> t
[1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> list(set(t))
[1, 2, 3, 5, 6, 7, 8]
>>> s = [1, 2, 3]
>>> list(set(t) - set(s))
[8, 5, 6, 7]

Jak widać z przykładowego wyniku, oryginalne zamówienie nie jest utrzymywane . Jak wspomniano powyżej, same zestawy są kolekcjami nieuporządkowanymi, więc zamówienie zostaje utracone. Podczas konwersji zestawu z powrotem na listę tworzona jest dowolna kolejność.

Utrzymanie porządku

Jeśli kolejność jest dla Ciebie ważna, będziesz musiał użyć innego mechanizmu. Bardzo częstym rozwiązaniem jest poleganie na OrderedDictutrzymywaniu kolejności kluczy podczas wstawiania:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

Począwszy od Python 3.7 , wbudowany słownik gwarantuje również zachowanie kolejności wstawiania, więc możesz również użyć tego bezpośrednio, jeśli korzystasz z Python 3.7 lub nowszej wersji (lub CPython 3.6):

>>> list(dict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

Zauważ, że może to wiązać się z pewnym nakładem na utworzenie słownika, a następnie utworzenie listy z niego. Jeśli tak naprawdę nie musisz zachowywać porządku, często lepiej jest użyć zestawu, zwłaszcza, że ​​daje dużo więcej operacji do pracy. Sprawdź to pytanie, aby uzyskać więcej informacji i alternatywne sposoby zachowania porządku podczas usuwania duplikatów.


Na koniec zauważ, że zarówno rozwiązania, setjak i OrderedDict/ dictwymagają, aby twoje przedmioty mogły być haszowalne . Zazwyczaj oznacza to, że muszą być niezmienne. Jeśli masz do czynienia z elementami, które nie są haszowalne (np. Obiekty z listy), będziesz musiał zastosować powolne podejście, w którym zasadniczo będziesz musiał porównać każdy element z każdym innym elementem w zagnieżdżonej pętli.

szturchać
źródło
4
Nie działa to w przypadku elementów listy, których nie można mieszać (np. Listy list)
KNejad,
3
@KNejad Tak mówi ostatni akapit.
poke
Ojej. Powinienem był przeczytać całość. Skończyło się na użyciu krotek zamiast list, aby to podejście mogło nadal działać.
KNejad
dodaj to do przykładu, t = [3, 2, 1, 1, 2, 5, 6, 7, 8], wyraźnie pokazuje różnicę!
sailfish009,
„... narzut związany z tworzeniem słownika w pierwszej kolejności… Jeśli tak naprawdę nie musisz zachowywać kolejności, lepiej jest użyć zestawu”. - Wyprofilowałem to, ponieważ byłem ciekaw, czy to rzeczywiście prawda. Moje czasy wskazują, że rzeczywiście zestaw jest nieco szybszy: 1,12 µs na pętlę (zestaw) vs 1,53 µs na pętlę (dyktowanie) w pętli 1M z absolutną różnicą czasową około 4s w 1 iteracji. Więc jeśli robisz to w ciasnej wewnętrznej pętli, możesz się tym przejmować, w przeciwnym razie prawdopodobnie nie.
millerdev,
414

W Pythonie 2.7 nowy sposób usuwania duplikatów z iterowalnych przy jednoczesnym zachowaniu ich w oryginalnej kolejności to:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

W Pythonie 3.5 OrDERDict ma implementację C. Moje czasy wskazują, że jest to zarówno najszybsze, jak i najkrótsze z różnych podejść do Pythona 3.5.

W Pythonie 3.6 zwykły słownik stał się uporządkowany i zwarty. (Ta funkcja dotyczy CPython i PyPy, ale może nie występować w innych implementacjach). To daje nam nowy najszybszy sposób deduplikacji przy zachowaniu porządku:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

W Pythonie 3.7 regularny słownik jest gwarantowany zarówno we wszystkich implementacjach. Zatem najkrótszym i najszybszym rozwiązaniem jest:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']
Raymond Hettinger
źródło
10
Myślę, że to jedyny sposób na utrzymanie porządku.
Herberth Amaral
19
@HerberthAmaral: To bardzo dalekie od prawdy, zobacz Jak usunąć duplikaty z listy w Pythonie, zachowując porządek?
Martijn Pieters
5
@MartijnPieters Korygowanie: Myślę, że to jedyny prosty sposób na utrzymanie porządku.
Herberth Amaral
11
Również w tym celu zawartość oryginalnej listy musi być haszowalna
Davide
Jak wspomniano w @Davide, oryginalna lista musi być mieszalna. Oznacza to, że nie działa to w przypadku listy słowników. TypeError: unhashable type: 'dictlist'
CraZ
186

Jest to jedna linijka: list(set(source_list))załatwi sprawę.

Jest setto coś, co nie może mieć duplikatów.

Aktualizacja: podejście zachowujące porządek składa się z dwóch linii:

from collections import OrderedDict
OrderedDict((x, True) for x in source_list).keys()

W tym przypadku wykorzystujemy fakt, że OrderedDictzapamiętuje kolejność wstawiania kluczy i nie zmienia go, gdy wartość określonego klucza jest aktualizowana. Wstawiamy Truejako wartości, ale możemy wstawić wszystko, wartości po prostu nie są używane. ( setdziała podobnie jak dictz ignorowanymi wartościami).

9000
źródło
4
Działa to tylko wtedy, gdy source_listjest możliwe do skrótu.
Adrian Keister
@AdrianKeister: To prawda. Istnieją obiekty, które mają rozsądną semantykę równości, ale których nie można mieszać, np. Listy. OTOH, jeśli nie możemy mieć skrótu takiego jak hastable, otrzymujemy kwadratowy algorytm po prostu porównujący każdy element ze wszystkimi znanymi obecnie unikalnymi elementami. Może to być całkowicie OK w przypadku krótkich danych wejściowych, szczególnie w przypadku wielu duplikatów.
9000
Dokładnie tak. Myślę, że twoja odpowiedź byłaby lepsza, gdybyś wziął pod uwagę ten bardzo powszechny przypadek użycia.
Adrian Keister
94
>>> t = [1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> t
[1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> s = []
>>> for i in t:
       if i not in s:
          s.append(i)
>>> s
[1, 2, 3, 5, 6, 7, 8]
Neeraj
źródło
33
Zauważ, że ta metoda działa w czasie O (n ^ 2) i dlatego jest bardzo wolna na dużych listach.
dotancohen
@Chris_Rands: Nie jestem pewien, czy frozensetdziała z zawartością, której nie można mieszać Podczas używania nadal pojawia się błąd, którego nie da się ukryć frozenset.
Adrian Keister
85

Jeśli nie zależy ci na zamówieniu, po prostu zrób to:

def remove_duplicates(l):
    return list(set(l))

setGwarantuje nie ma duplikatów.

Brendan Long
źródło
3
Nie działa, chyba że ljest możliwe do skrótu.
Adrian Keister
41

Aby utworzyć nową listę zachowującą kolejność pierwszych elementów duplikatów w L

newlist=[ii for n,ii in enumerate(L) if ii not in L[:n]]

na przykład if L=[1, 2, 2, 3, 4, 2, 4, 3, 5]wtedy newlistbędzie[1,2,3,4,5]

To sprawdza, czy każdy nowy element nie pojawiał się wcześniej na liście przed dodaniem go. Nie potrzebuje też importu.

Richard Fredlund
źródło
3
Ma to złożoność czasową O (n ^ 2) . Odpowiedzi z seti OrderedDictmogą mieć mniejszą zamortyzowaną złożoność czasu.
blubberdiblub
Użyłem w swoim kodzie tego rozwiązania i działało świetnie, ale myślę, że jest to czasochłonne
Gerasimos Ragavanis
@blubberdiblub czy możesz wyjaśnić, jaki mechanizm wydajniejszy w kodzie istnieje w zestawie i OrDERDict, który może sprawić, że będą mniej czasochłonne? (bez obciążenia związanego z ich ładowaniem)
ilias iliadis
@iliasiliadis Zwykłe implementacje zestawów i dyktu wykorzystują skróty lub (pewną formę zrównoważonych) drzew. Musisz rozważyć zbudowanie zbioru lub dykt i przeszukanie go (wiele razy), ale ich zamortyzowana złożoność zwykle jest wciąż niższa niż O (n ^ 2) . „Amortyzowane” w prostych słowach oznaczają średnio (mogą mieć najgorsze przypadki o większej złożoności niż przeciętny przypadek). Jest to istotne tylko wtedy, gdy masz dużą liczbę przedmiotów.
blubberdiblub
25

Kolega wysłał mi dzisiaj zaakceptowaną odpowiedź w ramach swojego kodu w celu zapoznania się z kodem. Choć z pewnością podziwiam elegancję odpowiedzi, o której mowa, nie jestem zadowolony z tego przedstawienia. Wypróbowałem to rozwiązanie (używam zestawu, aby skrócić czas wyszukiwania)

def ordered_set(in_list):
    out_list = []
    added = set()
    for val in in_list:
        if not val in added:
            out_list.append(val)
            added.add(val)
    return out_list

Aby porównać wydajność, wykorzystałem losową próbkę 100 liczb całkowitych - 62 były wyjątkowe

from random import randint
x = [randint(0,100) for _ in xrange(100)]

In [131]: len(set(x))
Out[131]: 62

Oto wyniki pomiarów

In [129]: %timeit list(OrderedDict.fromkeys(x))
10000 loops, best of 3: 86.4 us per loop

In [130]: %timeit ordered_set(x)
100000 loops, best of 3: 15.1 us per loop

Co się stanie, jeśli zestaw zostanie usunięty z rozwiązania?

def ordered_set(inlist):
    out_list = []
    for val in inlist:
        if not val in out_list:
            out_list.append(val)
    return out_list

Rezultat nie jest tak zły, jak w przypadku zamówienia OrDERDict , ale wciąż więcej niż 3 razy w porównaniu z oryginalnym rozwiązaniem

In [136]: %timeit ordered_set(x)
10000 loops, best of 3: 52.6 us per loop
wulkan
źródło
Miło jest użyć zestawu szybkiego wyszukiwania, aby przyspieszyć porównywanie w pętli. Jeśli kolejność nie ma znaczenia lista (set (x)) jest nadal 6 razy szybsza
Joop
@Joop, to było moje pierwsze pytanie do mojego kolegi - kolejność ma znaczenie; inaczej byłby to trywialny problem
wulkan
zoptymalizowana wersja zamówionego zestawu dla każdego zainteresowanego def unique(iterable)::; seen = set(); seen_add = seen.add; return [item for item in iterable if not item in seen and not seen_add(item)]
DrD
25

Istnieją również rozwiązania wykorzystujące Pandy i Numpy. Oba zwracają tablicę numpy, więc musisz użyć funkcji, .tolist()jeśli chcesz mieć listę.

t=['a','a','b','b','b','c','c','c']
t2= ['c','c','b','b','b','a','a','a']

Rozwiązanie Pandy

Korzystanie z funkcji Pandy unique():

import pandas as pd
pd.unique(t).tolist()
>>>['a','b','c']
pd.unique(t2).tolist()
>>>['c','b','a']

Rozwiązanie Numpy

Korzystanie z funkcji numpy unique().

import numpy as np
np.unique(t).tolist()
>>>['a','b','c']
np.unique(t2).tolist()
>>>['a','b','c']

Zauważ, że numpy.unique () również sortuje wartości . Lista t2jest więc sortowana. Jeśli chcesz zachować porządek, skorzystaj z następującej odpowiedzi :

_, idx = np.unique(t2, return_index=True)
t2[np.sort(idx)].tolist()
>>>['c','b','a']

Rozwiązanie nie jest tak eleganckie w porównaniu z innymi, jednak w porównaniu z pandas.unique (), numpy.unique () pozwala również sprawdzić, czy zagnieżdżone tablice są unikalne wzdłuż jednej wybranej osi.

GM
źródło
Spowoduje to przekształcenie listy w tablicę numpy, która jest bałaganem i nie będzie działać dla ciągów.
user227666
1
@ user227666 dziękuję za recenzję, ale to nieprawda, że ​​działa nawet z łańcuchem i możesz dodać .tolist, jeśli chcesz uzyskać listę ...
GM
1
Myślę, że to trochę jak próba zabicia pszczoły młotem. Działa, jasne! Ale importowanie biblioteki tylko w tym celu może być trochę przesadzone, prawda?
Debosmit Ray,
@DebosmitRay może być przydatny, jeśli pracujesz w Data Science, gdzie zwykle pracujesz z Numpy i wiele razy musisz pracować z tablicą Numpy.
GM,
najlepsza odpowiedź w 2020 roku @DebosmitRay Mam nadzieję, że zmienisz zdanie i użyjesz numpy / pand za każdym razem, gdy możesz
Egos
21

Kolejny sposób:

>>> seq = [1,2,3,'a', 'a', 1,2]
>> dict.fromkeys(seq).keys()
['a', 1, 2, 3]
James Sapam
źródło
1
Zauważ, że we współczesnych wersjach Pythona (myślę, że w wersji 2.7+, ale nie pamiętam na pewno), keys()zwraca obiekt widoku słownika, a nie listę.
Dustin Wyatt
16

Proste i łatwe:

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanlist = []
[cleanlist.append(x) for x in myList if x not in cleanlist]

Wynik:

>>> cleanlist 
[1, 2, 3, 5, 6, 7, 8]
Nima Soroush
źródło
5
kwadratowa złożoność mimo to - injest operacją O (n) i cleanlistbędziesz mieć co najwyżej nliczby => najgorszy przypadek ~ O (n ^ 2)
jermenkoo
6
Wyrażeń z listy nie należy stosować w przypadku skutków ubocznych.
Jean-François Fabre
13

W tej odpowiedzi będą dwie sekcje: Dwa unikalne rozwiązania i wykres prędkości dla konkretnych rozwiązań.

Usuwanie zduplikowanych elementów

Większość z tych odpowiedzi usuwa tylko zduplikowane elementy, które można haszować , ale to pytanie nie oznacza, że ​​nie tylko potrzebują haszowanych przedmiotów, co oznacza, że ​​zaoferuję niektóre rozwiązania, które nie wymagają haszowania .

collections.Counter to potężne narzędzie w standardowej bibliotece, które może być do tego idealne. Jest tylko jedno inne rozwiązanie, które zawiera nawet Licznik. Jednak to rozwiązanie ogranicza się również do kluczy mieszalnych .

Aby zezwolić na klucze nieukrywalne w Counter, stworzyłem klasę Container, która spróbuje uzyskać domyślną funkcję skrótu obiektu, ale jeśli zawiedzie, spróbuje użyć funkcji tożsamości. Definiuje także metodę eq i metodę skrótu . To powinno wystarczyć, aby pozwolić naszym produktom na niewymagalne elementy. Obiekty, których nie można skasować, będą traktowane tak, jakby można je było haszować. Jednak ta funkcja skrótu używa tożsamości dla obiektów nieukończonych, co oznacza, że ​​dwa równe obiekty, których oba są nieukończalne, nie będą działać. Sugeruję zastąpienie tego i zmianę go w celu użycia skrótu równoważnego typu zmiennego (np. Użycie hash(tuple(my_list))if my_listjest listą).

Stworzyłem również dwa rozwiązania. Kolejne rozwiązanie, które utrzymuje kolejność elementów, wykorzystując podklasę OrDERDict i Counter o nazwie „OrdersCounter”. Teraz oto funkcje:

from collections import OrderedDict, Counter

class Container:
    def __init__(self, obj):
        self.obj = obj
    def __eq__(self, obj):
        return self.obj == obj
    def __hash__(self):
        try:
            return hash(self.obj)
        except:
            return id(self.obj)

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

def remd(sequence):
    cnt = Counter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

def oremd(sequence):
    cnt = OrderedCounter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

remd to sortowanie bez uporządkowania, oremd to sortowanie uporządkowane. Możesz wyraźnie powiedzieć, który jest szybszy, ale i tak wyjaśnię. Nieuporządkowane sortowanie jest nieco szybsze. Przechowuje mniej danych, ponieważ nie potrzebuje porządku.

Teraz chciałem też pokazać porównanie prędkości dla każdej odpowiedzi. Zrobię to teraz.

Która funkcja jest najszybsza?

Do usuwania duplikatów zebrałem 10 funkcji z kilku odpowiedzi. Obliczyłem prędkość każdej funkcji i umieściłem ją na wykresie za pomocą matplotlib.pyplot .

Podzieliłem to na trzy rundy wykresów. Hashable to dowolny obiekt, który może być haszowany, hashable to każdy obiekt, który nie może być haszowany. Sekwencja uporządkowana to sekwencja, która zachowuje porządek, sekwencja nieuporządkowana nie zachowuje porządku. Oto kilka innych terminów:

Unordered Hashable był dla każdej metody, która usuwa duplikaty, co niekoniecznie musi zachowywać porządek. Nie musiało to działać na rzeczy nieskrępowane, ale mogło.

Uporządkowany Hashable był dla każdej metody, która zachowywała kolejność pozycji na liście, ale nie musiał działać dla nieusuwalnych, ale mógł.

Order Unhashable to dowolna metoda, która zachowuje porządek pozycji na liście i działa na niehashable.

Na osi y jest czas w sekundach.

Na osi X znajduje się liczba, do której zastosowano funkcję.

Wygenerowaliśmy sekwencje dla nieuporządkowanych skrótów i uporządkowanych skrótów z następującym zrozumieniem: [list(range(x)) + list(range(x)) for x in range(0, 1000, 10)]

W przypadku zamówionych elementów nieukończonych: [[list(range(y)) + list(range(y)) for y in range(x)] for x in range(0, 1000, 10)]

Zauważ, że w zakresie jest „krok”, ponieważ bez niego zajęłoby to 10 razy więcej. Również dlatego, że moim osobistym zdaniem myślałem, że może to wyglądać trochę łatwiej.

Zwróć też uwagę, że klawisze legendy są tym, co starałem się odgadnąć jako najbardziej istotne części funkcji. Co do funkcji, która jest najgorsza lub najlepsza? Wykres mówi sam za siebie.

Po ustaleniu, oto wykresy.

Nieuporządkowane haszysze

wprowadź opis zdjęcia tutaj (Zbliżony) wprowadź opis zdjęcia tutaj

Zamówione haszysze

wprowadź opis zdjęcia tutaj (Zbliżony) wprowadź opis zdjęcia tutaj

Zamówione Unhashables

wprowadź opis zdjęcia tutaj (Zbliżony) wprowadź opis zdjęcia tutaj

Corman
źródło
11

Miałem na liście dykt, więc nie mogłem zastosować powyższego podejścia. Dostałem błąd:

TypeError: unhashable type:

Więc jeśli zależy Ci na zamówieniu i / lub niektóre przedmioty są nie do zniesienia . Może się to przydać:

def make_unique(original_list):
    unique_list = []
    [unique_list.append(obj) for obj in original_list if obj not in unique_list]
    return unique_list

Niektórzy mogą uważać, że zrozumienie listy ze skutkiem ubocznym nie jest dobrym rozwiązaniem. Oto alternatywa:

def make_unique(original_list):
    unique_list = []
    map(lambda x: unique_list.append(x) if (x not in unique_list) else False, original_list)
    return unique_list
Cchristelis
źródło
6
mapze skutkiem ubocznym jest jeszcze bardziej mylące niż lista porównująca ze skutkiem ubocznym. Ponadto, lambda x: unique_list.append(x)jest tylko clunkier i wolniejszy sposób przekazać unique_list.append.
abarnert
Bardzo przydatny sposób dodawania elementów w jednym wierszu, dzięki!
ZLNK
2
@ZLNK, proszę, nigdy tego nie używaj. Oprócz bycia koncepcyjnie brzydkim, jest również wyjątkowo nieefektywny, ponieważ faktycznie tworzysz potencjalnie dużą listę i wyrzucasz ją tylko po to, aby wykonać podstawową iterację.
Eli Korvigo,
10

Wszystkie podejścia do utrzymywania porządku, które do tej pory widziałem, wykorzystują albo naiwne porównanie (w najlepszym razie złożoności czasowej O (n ^ 2)), albo kombinacje ciężkie OrderedDicts/ set+, listktóre są ograniczone do możliwych do wprowadzenia wartości. Oto niezależne od skrótów rozwiązanie O (nlogn):

Aktualizacja dodała keyargument, dokumentację i zgodność z Python 3.

# from functools import reduce <-- add this import on Python 3

def uniq(iterable, key=lambda x: x):
    """
    Remove duplicates from an iterable. Preserves order. 
    :type iterable: Iterable[Ord => A]
    :param iterable: an iterable of objects of any orderable type
    :type key: Callable[A] -> (Ord => B)
    :param key: optional argument; by default an item (A) is discarded 
    if another item (B), such that A == B, has already been encountered and taken. 
    If you provide a key, this condition changes to key(A) == key(B); the callable 
    must return orderable objects.
    """
    # Enumerate the list to restore order lately; reduce the sorted list; restore order
    def append_unique(acc, item):
        return acc if key(acc[-1][1]) == key(item[1]) else acc.append(item) or acc 
    srt_enum = sorted(enumerate(iterable), key=lambda item: key(item[1]))
    return [item[1] for item in sorted(reduce(append_unique, srt_enum, [srt_enum[0]]))] 
Eli Korvigo
źródło
Jednak to rozwiązanie wymaga zamawiania elementów. Użyję go, aby ujednolicić moją listę list: utrudnia to tuple()listy i ich haszowanie. | | | | - Ogólnie rzecz biorąc, proces mieszania zajmuje czas proporcjonalny do wielkości całych danych, podczas gdy to rozwiązanie zajmuje czas O (nlog (n)), zależnie tylko od długości listy.
loxaxs
Myślę, że podejście oparte na zestawie jest równie tanie (O (n log n)) lub tańsze niż sortowanie + wykrywanie unikatów. (Podejście to byłoby jednak znacznie lepsze równoległe). Nie zachowuje również dokładnie pierwotnego porządku, ale daje porządek przewidywalny.
9000
@ 9000 To prawda. Nigdy nie wspominałem o złożoności czasowej podejścia opartego na tabeli skrótów, którym jest oczywiście O (n). Tutaj można znaleźć wiele odpowiedzi zawierających tabele skrótów. Nie są jednak uniwersalne, ponieważ wymagają obiektów umożliwiających haszowanie. Co więcej, wymagają dużo więcej pamięci.
Eli Korvigo,
Poświęcenie czasu na przeczytanie i zrozumienie tej odpowiedzi. Czy warto wyliczać, kiedy nie używasz indeksów? reduce() Pracuje już nad posortowanej kolekcji srt_enum, dlaczego zastosować sortedponownie?
Brayoni
@Brayoni pierwsze sortowanie służy do grupowania równych wartości, drugie sortowanie ma na celu przywrócenie początkowej kolejności. Wyliczenie jest potrzebne do śledzenia pierwotnej kolejności względnej.
Eli Korvigo
9

Jeśli chcesz zachować porządek i nie używać żadnych modułów zewnętrznych, możesz to zrobić w prosty sposób:

>>> t = [1, 9, 2, 3, 4, 5, 3, 6, 7, 5, 8, 9]
>>> list(dict.fromkeys(t))
[1, 9, 2, 3, 4, 5, 6, 7, 8]

Uwaga: Ta metoda zachowuje kolejność pojawiania się, więc, jak pokazano powyżej, dziewięć pojawi się po jednym, ponieważ był to pierwszy raz, gdy się pojawił. Jest to jednak taki sam wynik, jak w przypadku robienia

from collections import OrderedDict
ulist=list(OrderedDict.fromkeys(l))

ale jest znacznie krótszy i działa szybciej.

Działa to, ponieważ za każdym razem, gdy fromkeysfunkcja próbuje utworzyć nowy klucz, jeśli wartość już istnieje, po prostu ją zastąpi. Nie ma to jednak fromkeysżadnego wpływu na słownik, ponieważ tworzy słownik, w którym wszystkie klucze mają wartość None, więc w ten sposób skutecznie eliminuje wszystkie duplikaty.

HEEL_caT666
źródło
Wypróbuj również tutaj
vineeshvs
8

Możesz także to zrobić:

>>> t = [1, 2, 3, 3, 2, 4, 5, 6]
>>> s = [x for i, x in enumerate(t) if i == t.index(x)]
>>> s
[1, 2, 3, 4, 5, 6]

Powodem tego jest to, że indexmetoda zwraca tylko pierwszy indeks elementu. Zduplikowane elementy mają wyższe wskaźniki. Zobacz tutaj :

list.index (x [, start [, koniec]])
Zwraca liczony od zera indeks na liście pierwszego elementu, którego wartość wynosi x. Podnosi ValueError, jeśli nie ma takiego elementu.

Atonalny
źródło
To jest okropnie nieefektywne. list.indexjest operacją w czasie liniowym, dzięki czemu Twoje rozwiązanie jest kwadratowe.
Eli Korvigo,
Masz rację. Ale również uważam, że to dość oczywiste, że rozwiązanie ma być liniową linią, która zachowuje porządek. Cała reszta już tu jest.
Atonal,
7

Spróbuj użyć zestawów:

import sets
t = sets.Set(['a', 'b', 'c', 'd'])
t1 = sets.Set(['a', 'b', 'c'])

print t | t1
print t - t1
Charlie Martin
źródło
7

Zredukuj wariant z zamówieniem zachowaj:

Załóżmy, że mamy listę:

l = [5, 6, 6, 1, 1, 2, 2, 3, 4]

Zredukuj wariant (nieefektywny):

>>> reduce(lambda r, v: v in r and r or r + [v], l, [])
[5, 6, 1, 2, 3, 4]

5 x szybszy, ale bardziej wyrafinowany

>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

Wyjaśnienie:

default = (list(), set())
# user list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

reduce(reducer, l, default)[0]
Sergey M. Nikitin
źródło
7

Najlepszym podejściem do usuwania duplikatów z listy jest użycie funkcji set () dostępnej w Pythonie, ponownie konwertując ten zestaw na listę

In [2]: some_list = ['a','a','v','v','v','c','c','d']
In [3]: list(set(some_list))
Out[3]: ['a', 'c', 'd', 'v']
Anurag Misra
źródło
@MeetZaveri zadowolony.!
Anurag Misra
Tworzenie nowych list i zestawów nie jest darmowe. Co się stanie, jeśli zrobimy to wiele razy w krótkich odstępach czasu (tj. W bardzo ciasnej pętli), a listy będą bardzo małe?
Z4-tier
6

Możesz użyć następującej funkcji:

def rem_dupes(dup_list): 
    yooneeks = [] 
    for elem in dup_list: 
        if elem not in yooneeks: 
            yooneeks.append(elem) 
    return yooneeks

Przykład :

my_list = ['this','is','a','list','with','dupicates','in', 'the', 'list']

Stosowanie:

rem_dupes(my_list)

[„this”, „is”, „a”, „list”, „with”, „duplicates”, „in”, „the”]

Cybernetyczny
źródło
5

Istnieje wiele innych odpowiedzi sugerujących różne sposoby na zrobienie tego, ale wszystkie są operacjami wsadowymi, a niektóre z nich odrzucają oryginalne zamówienie. Może to być w porządku w zależności od potrzeb, ale jeśli chcesz iterować wartości w kolejności pierwszej instancji każdej wartości i chcesz usunąć duplikaty w locie w porównaniu do wszystkich naraz, możesz użyć ten generator:

def uniqify(iterable):
    seen = set()
    for item in iterable:
        if item not in seen:
            seen.add(item)
            yield item

Zwraca generator / iterator, dzięki czemu można go używać w dowolnym miejscu, w którym można użyć iteratora.

for unique_item in uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]):
    print(unique_item, end=' ')

print()

Wynik:

1 2 3 4 5 6 7 8

Jeśli chcesz list, możesz to zrobić:

unique_list = list(uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]))

print(unique_list)

Wynik:

[1, 2, 3, 4, 5, 6, 7, 8]
Cyphase
źródło
seen = set(iterable); for item in seen: yield itemjest prawie na pewno szybszy. (Nie próbowałem tego konkretnego przypadku, ale tak sądzę.)
dylnmc
2
@dylnmc, to operacja wsadowa, a także traci porządek. Moja odpowiedź była specjalnie zaprojektowana w locie i w kolejności pierwszego wystąpienia. :)
Cyphase
5

Bez użycia zestawu

data=[1, 2, 3, 1, 2, 5, 6, 7, 8]
uni_data=[]
for dat in data:
    if dat not in uni_data:
        uni_data.append(dat)

print(uni_data) 
Suresh Gupta
źródło
5

Możesz użyć setdo usunięcia duplikatów:

mylist = list(set(mylist))

Pamiętaj jednak, że wyniki będą nieuporządkowane. Jeśli to jest problem:

mylist.sort()
Flavio Wuensche
źródło
1
Możesz po prostu: mylist = sorted (list (set (mylist)))
Erik Campobadal
5

Jeszcze jednym lepszym podejściem może być

import pandas as pd

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanList = pd.Series(myList).drop_duplicates().tolist()
print(cleanList)

#> [1, 2, 3, 5, 6, 7, 8]

i porządek pozostaje zachowany.

Akarsh Jain
źródło
Chociaż może to działać dobrze, używanie do tego celu ciężkiej biblioteki, takiej jak pandy, wydaje się przesadą.
Glutexo,
4

Ten dba o zamówienie bez większych problemów (OrderdDict i inni). Prawdopodobnie nie jest to metoda najbardziej Pythońska, ani najkrótsza, ale polega na tym:

def remove_duplicates(list):
    ''' Removes duplicate items from a list '''
    singles_list = []
    for element in list:
        if element not in singles_list:
            singles_list.append(element)
    return singles_list
CGF
źródło
1. Nigdy nie powinieneś cienia wbudowanych nazw (przynajmniej tak ważne jak list); 2. Twoja metoda skaluje się bardzo źle: jest kwadratowa pod względem liczby elementów list.
Eli Korvigo,
1. Zgadza się, ale to był przykład; 2. Prawidłowo, i to jest dokładnie powód, dla którego to zaoferowałem. Wszystkie zamieszczone tutaj rozwiązania mają zalety i wady. Niektórzy poświęcają prostotę lub porządek, moje poświęcają skalowalność.
cgf
jest to algorytm „malarza Shlemiela” ...
Z4-tier
4

poniższy kod jest prosty do usunięcia duplikatu z listy

def remove_duplicates(x):
    a = []
    for i in x:
        if i not in a:
            a.append(i)
    return a

print remove_duplicates([1,2,2,3,3,4])

zwraca [1,2,3,4]

vinay hegde
źródło
2
Jeśli nie zależy ci na porządku, trwa to znacznie dłużej. list(set(..))(ponad 1 milion podań) pobije to rozwiązanie o około 10 pełnych sekund - podczas gdy takie podejście zajmuje około 12 sekund, list(set(..))zajmuje tylko około 2 sekund!
dylnmc,
@dylnmc jest to również kopia znacznie starszej odpowiedzi
Eli Korvigo
4

Oto najszybsze rozwiązanie python w porównaniu do innych wymienionych w odpowiedziach.

Wykorzystanie szczegółów implementacji oceny zwarć pozwala na użycie listowania, które jest wystarczająco szybkie. visited.add(item)zawsze zwraca Nonewynik, który jest oceniany jako False, więc prawa strona orzawsze będzie wynikiem takiego wyrażenia.

Czas sam

def deduplicate(sequence):
    visited = set()
    adder = visited.add  # get rid of qualification overhead
    out = [adder(item) or item for item in sequence if item not in visited]
    return out
Thodnev
źródło
4

Za pomocą zestawu :

a = [0,1,2,3,4,3,3,4]
a = list(set(a))
print a

Używając unikalnego :

import numpy as np
a = [0,1,2,3,4,3,3,4]
a = np.unique(a).tolist()
print a
Nurul Akter Towhid
źródło
4

Niestety. Większość odpowiedzi tutaj albo nie zachowuje kolejności, albo jest za długa. Oto prosta, zachowująca porządek odpowiedź.

s = [1,2,3,4,5,2,5,6,7,1,3,9,3,5]
x=[]

[x.append(i) for i in s if i not in x]
print(x)

To da ci x z usuniętymi duplikatami, ale zachowując kolejność.

ste_kwr
źródło
3

Bardzo prosty sposób w Pythonie 3:

>>> n = [1, 2, 3, 4, 1, 1]
>>> n
[1, 2, 3, 4, 1, 1]
>>> m = sorted(list(set(n)))
>>> m
[1, 2, 3, 4]
Wariored
źródło
2
sorted(list(...))jest redundantny ( sortedjuż domyślnie przekształca swój argument na nowy list, sortuje go, a następnie zwraca nowy list, więc używając obu środków, czyniąc niepotrzebne tymczasowe list). Używaj tylko listwtedy, gdy wynik nie musi być sortowany, używaj tylko sortedwtedy, gdy wynik wymaga sortowania.
ShadowRanger
3

Magia wbudowanego typu Python

W Pythonie bardzo łatwo jest przetwarzać tak skomplikowane przypadki, jak i tylko przy użyciu wbudowanego typu python.

Pokażę ci, jak to zrobić!

Metoda 1: Przypadek ogólny

Sposób ( 1 kod wiersza ), aby usunąć zduplikowany element z listy i nadal zachować porządek sortowania

line = [1, 2, 3, 1, 2, 5, 6, 7, 8]
new_line = sorted(set(line), key=line.index) # remove duplicated element
print(new_line)

Otrzymasz wynik

[1, 2, 3, 5, 6, 7, 8]

Metoda 2: Przypadek specjalny

TypeError: unhashable type: 'list'

Specjalny przypadek przetwarzania nieukończonego ( 3 kody linii )

line=[['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']]

tuple_line = [tuple(pt) for pt in line] # convert list of list into list of tuple
tuple_new_line = sorted(set(tuple_line),key=tuple_line.index) # remove duplicated element
new_line = [list(t) for t in tuple_new_line] # convert list of tuple into list of list

print (new_line)

Otrzymasz wynik:

[
  ['16.4966155686595', '-27.59776154691', '52.3786295521147'], 
  ['17.6508629295574', '-27.143305738671', '47.534955022564'], 
  ['18.8051102904552', '-26.688849930432', '42.6912804930134'], 
  ['19.5504702331098', '-26.205884452727', '37.7709192714727'], 
  ['20.2929416861422', '-25.722717575124', '32.8500163147157']
]

Ponieważ krotka jest haszowalna i możesz łatwo konwertować dane między listą a krotką

Milo Chen
źródło