Come si rimuovono i duplicati da un elenco preservando l'ordine?


770

Esiste un built-in che rimuove i duplicati dall'elenco in Python, preservando l'ordine? So che posso usare un set per rimuovere i duplicati, ma questo distrugge l'ordine originale. So anche che posso rotolare il mio in questo modo:

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

(Grazie a rilassarsi per questo esempio di codice .)

Ma mi piacerebbe avvalermi di un linguaggio incorporato o più un linguaggio Pythonic, se possibile.

Domanda correlata: in Python, qual è l'algoritmo più veloce per rimuovere i duplicati da un elenco in modo che tutti gli elementi siano univoci mantenendo l'ordine ?

Risposte:


763

Ecco alcune alternative: http://www.peterbe.com/plog/uniqifiers-benchmark

Il più veloce:

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

Perché assegnare seen.addal seen_addinvece di chiamare seen.add? Python è un linguaggio dinamico e risolvere seen.addogni iterazione è più costoso della risoluzione di una variabile locale. seen.addpotrebbe essere cambiato tra le iterazioni e il runtime non è abbastanza intelligente da escluderlo. Per giocare in sicurezza, deve controllare l'oggetto ogni volta.

Se si prevede di utilizzare questa funzione molto sullo stesso set di dati, forse sarebbe meglio con un set ordinato: http://code.activestate.com/recipes/528878/

O (1) inserimento, cancellazione e controllo membri per operazione.

(Piccola nota aggiuntiva: seen.add()ritorna sempre None, quindi quanto orsopra è disponibile solo come modo per tentare un aggiornamento impostato e non come parte integrante del test logico.)


20
@JesseDhillon seen.addavrebbe potuto cambiare tra le iterazioni e il runtime non è abbastanza intelligente da escluderlo. Per giocare in sicurezza, deve controllare l'oggetto ogni volta. - Se guardi il bytecode con dis.dis(f), puoi vedere che viene eseguito LOAD_ATTRper il addmembro su ogni iterazione. ideone.com/tz1Tll
Markus Jarderot

5
Quando provo questo su un elenco di liste ottengo: TypeError: unhashable tipo: 'list'
Jens Timmerman

7
La tua soluzione non è la più veloce. In Python 3 (non testato 2) questo è più veloce (elenco di 300k voci - 0,045s (il tuo) vs 0,035s (questo): visto = set (); restituisce [x per x in righe se x non è visto e non seen.add (x)]. Non ho trovato alcun effetto sulla velocità della linea seen_add che hai fatto
user136036

3
@ user136036 Link ai test. Quante volte li hai eseguiti? seen_addè un miglioramento, ma i tempi possono essere influenzati dalle risorse di sistema al momento. Sarebbe interessato a vedere gli orari completi
jamylak,

2
Per chiunque stia scrivendo codice Python, dovresti davvero pensarci due volte prima di sacrificare la leggibilità e le convenzioni Python comunemente concordate solo per spremere qualche nanosecondo in più per ciclo. Testare con e senza seen_add = seen.addproduce solo un aumento della velocità dell'1%. È poco significativo.
slitta

343

Modifica 2016

Come ha sottolineato Raymond , in Python 3.5+ doveOrderedDict è implementato in C, l'approccio alla comprensione dell'elenco sarà più lento di OrderedDict(a meno che non sia effettivamente necessario l'elenco alla fine - e anche in questo caso, solo se l'input è molto breve). Quindi la migliore soluzione per 3.5+ è OrderedDict.

Importante Modifica 2015

Come osserva @abarnert , la more_itertoolslibreria ( pip install more_itertools) contiene una unique_everseenfunzione creata per risolvere questo problema senza mutazioni illeggibili ( not seen.add) nella comprensione dell'elenco. Questa è anche la soluzione più veloce:

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

Solo una semplice importazione di librerie e nessun hack. Questo deriva da un'implementazione della ricetta itertools unique_everseenche assomiglia a:

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

In Python 2.7+l' idioma comune accettato (che funziona ma non è ottimizzato per la velocità, ora vorrei usare unique_everseen) per questo usi collections.OrderedDict:

Durata: O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

Sembra molto più bello di:

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

e non utilizza il brutto hack :

not seen.add(x)

che si basa sul fatto che si set.addtratta di un metodo sul posto che restituisce sempreNone quindi not Nonevaluta True.

Si noti tuttavia che la soluzione di hacking è più veloce nella velocità raw sebbene abbia la stessa complessità di runtime O (N).


5
Conversione in un tipo personalizzato di dict solo per prendere le chiavi? Solo un'altra stampella.
Nakilon,

3
@Nakilon Non vedo davvero come sia una stampella. Non espone alcuno stato mutabile, quindi è molto pulito in questo senso. Internamente, i set di Python sono implementati con dict () ( stackoverflow.com/questions/3949310/… ), quindi in pratica stai solo facendo ciò che l'interprete avrebbe fatto comunque.
Imran,

Usa solo gli effetti collaterali e fai [seen.add(x) for x in seq if x not in seen], o se non ti piacciono gli effetti collaterali di comprensione usa solo un forloop: for x in seq: seen.add(x) if x not in seen else None(sempre un one-liner, anche se in questo caso penso che one-liner-ness sia una proprietà sciocca da provare in un soluzione
ely

@EMS Questo non conserva l'ordine. Puoi fare altrettanto seen = set(seq).
terremoto del

1
@CommuSoft Sono d'accordo, anche se praticamente è quasi sempre O (n) a causa del caso peggiore super altamente improbabile
jamylak,

110

In Python 2.7 , il nuovo modo di rimuovere i duplicati da un iterabile mantenendolo nell'ordine originale è:

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

In Python 3.5 , OrderedDict ha un'implementazione C. I miei tempi mostrano che questo è ora il più veloce e il più breve dei vari approcci per Python 3.5.

In Python 3.6 , il dict regolare divenne sia ordinato che compatto. (Questa funzione è valida per CPython e PyPy ma potrebbe non essere presente in altre implementazioni). Questo ci dà un nuovo modo più veloce di dedurre mantenendo l'ordine:

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

In Python 3.7 , il dict regolare è garantito per entrambi ordinato in tutte le implementazioni. Quindi, la soluzione più breve e veloce è:

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

Risposta a @max: una volta passati a 3.6 o 3.7 e usando il dict normale invece di OrderedDict , non puoi davvero battere la performance in nessun altro modo. Il dizionario è denso e si converte prontamente in un elenco quasi senza spese generali. L'elenco di destinazione è pre-dimensionato in len (d) che salva tutti i ridimensionamenti che si verificano in una comprensione dell'elenco. Inoltre, poiché l'elenco delle chiavi interne è denso, la copia dei puntatori è quasi veloce come una copia dell'elenco.


È più veloce di qualsiasi altro approccio sulla mia macchina (python 3.5) purché alla fine non mi converta OrderedDictin un elenco. Se devo convertirlo in un elenco, per piccoli input l'approccio alla comprensione dell'elenco è ancora più veloce fino a 1,5 volte. Detto questo, questa soluzione è molto più pulita.
massimo

7
L'unico problema è che gli "elementi" iterabili devono essere hash - sarebbe bello avere l'equivalente per gli iterabili con elementi arbitrari (come un elenco di elenchi)
Mr_and_Mrs_D

L'iterazione dell'ordine di inserzione su un dict offre funzionalità che servono più casi d'uso rispetto alla rimozione di duplicati. Ad esempio, le analisi scientifiche si basano su calcoli riproducibili che l'iterazione di dettatura non deterministica non supporta. La riproducibilità è uno dei principali obiettivi attuali nella modellazione scientifica computazionale, quindi accogliamo con favore questa nuova funzionalità. Anche se so che è banale costruire con un dettato deterministico, un deterministico ad alte prestazioni set()aiuterebbe gli utenti più ingenui a sviluppare codici riproducibili.
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

unico → ['1', '2', '3', '6', '4', '5']


28
Vale la pena notare che questo funziona inn^2
goncalopp

25
Ick. 2 avvertimenti: utilizzo di un elenco per il test di appartenenza (lento, O (N)) e utilizzo di un elenco di comprensione per gli effetti collaterali (creazione di un altro elenco di Noneriferimenti nel processo!)
Martijn Pieters

1
Sono d'accordo con @MartijnPieters non c'è assolutamente alcun motivo per la comprensione dell'elenco con effetti collaterali. Basta usare un forloop invece
jamylak,

31

Non calciare un cavallo morto (questa domanda è molto vecchia e ha già molte buone risposte), ma ecco una soluzione che usa i panda che è abbastanza veloce in molte circostanze ed è morto semplice da usare.

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

L'elenco non deve nemmeno essere ordinato , la condizione sufficiente è che valori uguali siano raggruppati insieme.

Modifica: ho ipotizzato che "preservare l'ordine" implichi che l'elenco sia effettivamente ordinato. In caso contrario, la soluzione di MizardX è quella giusta.

Modifica della community: questo è comunque il modo più elegante per "comprimere elementi consecutivi duplicati in un singolo elemento".


1
Ma questo non preserva l'ordine!

1
Hm, questo è problematico, dal momento che non posso garantire che gli stessi valori siano raggruppati senza ricorrere in loop una volta sull'elenco, a quel punto avrei potuto eliminare i duplicati.
Josh Glover,

Supponevo che "preservare l'ordine" implicava che l'elenco fosse effettivamente ordinato.
Rafał Dowgird,

1
Forse le specifiche dell'elenco di input non sono chiare. Non è nemmeno necessario raggruppare i valori: [2, 1, 3, 1]. Quindi quali valori conservare e quali eliminare?

1
@igorkf Ignorando il secondo elemento della coppia o delle coppie.
Rafał Dowgird,

24

Penso che se vuoi mantenere l'ordine,

puoi provare questo:

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

O allo stesso modo puoi farlo:

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

Puoi anche fare questo:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

Può anche essere scritto come questo:

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

3
Le prime due risposte presuppongono che l'ordine dell'elenco possa essere ricostruito utilizzando una funzione di ordinamento, ma potrebbe non essere così.
Richard,

5
La maggior parte delle risposte si concentra sulle prestazioni. Per gli elenchi che non sono abbastanza grandi da preoccuparsi delle prestazioni, l'ordinato (set (list1), key = list1.index) è la cosa migliore che abbia mai visto. Nessuna importazione extra, nessuna funzione extra, nessuna variabile extra ed è abbastanza semplice e leggibile.
Derek Veit,

23

In Python 3.7 e versioni successive, i dizionari sono sicuri di ricordare il loro ordine di inserimento delle chiavi. La risposta a questa domanda riassume lo stato attuale delle cose.

La OrderedDictsoluzione diventa così obsoleta e senza dichiarazioni di importazione possiamo semplicemente emettere:

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

12

Per un'altra risposta molto tardi a un'altra domanda molto vecchia:

Le itertoolsricette hanno una funzione che lo fa, usando la seentecnica set, ma:

  • Gestisce una keyfunzione standard .
  • Non utilizza hack sconvenienti.
  • Ottimizza il loop pre-binding seen.addinvece di cercarlo N volte. ( f7fa anche questo, ma alcune versioni no.)
  • Ottimizza il loop usando ifilterfalse, quindi devi solo passare in loop sopra gli elementi unici in Python, anziché tutti. ( ifilterfalseOvviamente continui a iterarli tutti dentro , ovviamente, ma è in C e molto più velocemente.)

In realtà è più veloce di f7? Dipende dai tuoi dati, quindi dovrai provarli e vedere. Se vuoi un elenco alla fine, f7usa un listcomp e non c'è modo di farlo qui. (Puoi direttamente appendinvece di yielding, oppure puoi alimentare il generatore nellist funzione, ma nessuno dei due può essere veloce come il LIST_APPEND all'interno di un elenco comp.) Ad ogni modo, di solito, spremere qualche microsecondo non sarà come importante come avere una funzione facilmente comprensibile, riutilizzabile, già scritta che non richiede DSU quando si desidera decorare.

Come per tutte le ricette, è disponibile anche in more-iterools .

Se vuoi semplicemente il no keycase, puoi semplificarlo come:

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

Ho completamente ignorato che more-itertoolsquesta è chiaramente la risposta migliore. Un semplice from more_itertools import unique_everseen list(unique_everseen(items))Un approccio molto più veloce del mio e molto migliore della risposta accettata, penso che valga la pena scaricare la libreria. Vado alla community wiki la mia risposta e la aggiungo.
jamylak,

12

Giusto per aggiungere un altro (molto performante) implementazione di funzionalità quali un da un modulo esterno 1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

Tempi

Ho fatto alcune temporizzazioni (Python 3.6) e questi dimostrano che è più veloce di tutte le altre alternative che ho provato, tra cui OrderedDict.fromkeys, f7e more_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

inserisci qui la descrizione dell'immagine

E solo per essere sicuro di aver fatto anche un test con più duplicati solo per verificare se fa la differenza:

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

inserisci qui la descrizione dell'immagine

E uno contenente un solo valore:

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

inserisci qui la descrizione dell'immagine

In tutti questi casi la iteration_utilities.unique_everseenfunzione è la più veloce (sul mio computer).


Questa iteration_utilities.unique_everseenfunzione può anche gestire valori non lavabili nell'input (tuttavia con una O(n*n)prestazione anziché la O(n)prestazione quando i valori sono cancellabili).

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 Disclaimer: sono l'autore di quel pacchetto.


Non capisco la necessità per questa linea: seen_add = seen.add- è necessario per i parametri di riferimento?
Alex

@Alex Questo è l'approccio dato in questa risposta . Avrebbe più senso chiederlo lì. Ho appena usato l'approccio da quella risposta per confrontare i tempi.
MSeifert,

puoi aggiungere il dict.fromkeys()metodo al tuo grafico per favore?
Boris

Non sono davvero sicuro se avrò lo stesso per fare i tempi presto. Pensi che sia molto più veloce del ordereddict.fromkeys?
MSeifert

"Questa funzione iteration_utilities.unique_everseen può anche gestire valori non lavabili nell'input" - sì, questo è davvero importante. Se hai un elenco di dadi di dadi di dadi ecc, questo è l'unico modo per fare il lavoro, anche su piccola scala.
Roko Mijic,

6

Per nessun tipo hash (es. Elenco di elenchi), basato su MizardX:

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]

3

Prendendo in prestito l'idea ricorsiva utilizzata nella definizione della nubfunzione di Haskell per le liste, questo sarebbe un approccio ricorsivo:

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

per esempio:

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

L'ho provato per dimensioni dei dati crescenti e ho visto la complessità temporale sub-lineare (non definitiva, ma suggerisce che dovrebbe andare bene per i dati normali).

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

Penso anche che sia interessante che questo possa essere prontamente generalizzato all'unicità da altre operazioni. Come questo:

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

Ad esempio, potresti passare una funzione che utilizza la nozione di arrotondamento allo stesso numero intero come se fosse "uguaglianza" per scopi di unicità, come questo:

def test_round(x,y):
    return round(x) != round(y)

quindi unique (some_list, test_round) fornirebbe gli elementi unici dell'elenco in cui l'unicità non significava più l'uguaglianza tradizionale (che è implicita usando qualsiasi tipo di approccio basato su set o dict-key a questo problema) ma voleva invece prendere solo il primo elemento che viene arrotondato a K per ogni possibile numero intero K a cui gli elementi potrebbero arrotondare, ad esempio:

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

1
Si noti che le prestazioni peggioreranno quando il numero di elementi univoci è molto elevato rispetto al numero totale di elementi, poiché l'utilizzo di ogni chiamata ricorsiva successiva filtertrarrà a mala pena beneficio dalla chiamata precedente. Ma se il numero di elementi univoci è piccolo rispetto alla dimensione dell'array, questo dovrebbe funzionare abbastanza bene.
ely,

3

5 volte più veloce per ridurre la variante ma più sofisticato

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> 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]

Spiegazione:

default = (list(), set())
# use 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]
[5, 6, 1, 2, 3, 4]

3

È possibile fare riferimento a una comprensione dell'elenco in quanto è stata creata dal simbolo '_ [1]'.
Ad esempio, la seguente funzione rende unico un elenco di elementi senza modificarne l'ordine facendo riferimento alla sua comprensione dell'elenco.

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

demo:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

Produzione:

[1, 2, 3, 4, 5]

2
Si noti inoltre che lo renderebbe un'operazione O (n ^ 2), in cui la creazione di un set / dict (che ha un tempo di ricerca costante) e l'aggiunta di elementi non visti in precedenza sarà lineare.
ely,

Questo è Python 2.6, solo io credo. E sì, è O (N ^ 2)
jamylak,

2

La risposta di MizardX offre una buona raccolta di approcci multipli.

Questo è quello che mi è venuto in mente pensando ad alta voce:

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

La tua soluzione è carina, ma richiede l'ultima apparizione di ogni elemento. Per prendere la prima apparizione usa: [x per i, x in enumerato (lista mia) se x non nella mia lista [: i]]
Rivka

7
Poiché la ricerca in un elenco è O(n)un'operazione e la si esegue su ciascun elemento, la complessità risultante della soluzione sarebbe O(n^2). Questo è inaccettabile per un problema così banale.
Nikita Volkov,

2

ecco un modo semplice per farlo:

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

che dà l'output:

["hello", " ", "w", "o", "r", "l", "d"]

1

Potresti fare una specie di brutto hack di comprensione della lista.

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

Preferiscono i,e in enumerate(l)a l[i] for i in range(len(l)).
Evpok,

1

Approccio relativamente efficace con _sorted_un numpyarray:

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

Uscite:

array([ 1,  3,  8, 12])

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

Un'espressione del generatore che utilizza la O (1) cerca un set per determinare se includere o meno un elemento nel nuovo elenco.


1
Uso intelligente extendcon un'espressione del generatore che dipende dall'oggetto che viene esteso (quindi +1), ma set(n)viene ricalcolato in ogni fase (che è lineare) e questo ostacola l'approccio generale all'essere quadratico. In effetti, questo è quasi certamente peggio del semplice utilizzo ele in n. Fare un set per un singolo test di appartenenza non vale la spesa per la creazione del set. Comunque - è un approccio interessante.
John Coleman,

1

Una semplice soluzione ricorsiva:

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

1

Eliminare i valori duplicati in una sequenza, ma preservare l'ordine degli elementi rimanenti. Uso della funzione del generatore per uso generale.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

1

gli utenti di Panda dovrebbero verificare pandas.unique.

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

La funzione restituisce un array NumPy. Se necessario, è possibile convertirlo in un elenco con il tolistmetodo


1
Ben fatto. Non avrei mai immaginato di usare i panda per quello, ma funziona
seralouk

0

Se hai bisogno di una fodera, forse questo potrebbe aiutare:

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... dovrebbe funzionare ma correggimi se sbaglio


è un'espressione condizionale quindi è buona
code22

0

Se lo usi abitualmente pandase l'estetica è preferita alle prestazioni, considera la funzione integrata pandas.Series.drop_duplicates:

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

Timing:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop

0

ciò manterrà l'ordine e verrà eseguito in tempo O (n). fondamentalmente l'idea è quella di creare un buco ovunque ci sia un duplicato trovato e affondarlo fino in fondo. utilizza un puntatore di lettura e scrittura. ogni volta che viene trovato un duplicato, solo il puntatore di lettura avanza e il puntatore di scrittura rimane sulla voce duplicata per sovrascriverlo.

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]

0

Una soluzione senza utilizzare moduli o set importati:

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

Fornisce output:

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

questa è O (N ** 2) complessità + lista di sezioni ogni volta.
Jean-François Fabre

0

Un metodo sul posto

Questo metodo è quadratico, perché abbiamo una ricerca lineare nella lista per ogni elemento della lista (a ciò dobbiamo aggiungere il costo di riorganizzare la lista a causa della dels).

Detto questo, è possibile operare sul posto se iniziamo dalla fine della lista e procediamo verso l'origine rimuovendo ogni termine presente nella sotto-lista alla sua sinistra

Questa idea nel codice è semplicemente

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

Un semplice test di implementazione

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             

Prima di postare ho cercato invano nel corpo delle risposte "posto". Se altri hanno risolto il problema in modo simile, avvisami e rimuoverò la mia risposta al più presto.
gboffi,

Potresti usare solo l[:] = <one of the the faster methods>se volessi un'operazione sul posto, no?
timgeb

@timgeb Sì e no ... Quando lo faccio a=[1]; b=a; a[:]=[2]allora il b==[2]valore è Truee possiamo dire che lo stiamo facendo sul posto, tuttavia ciò che proponi è l'utilizzo di nuovo spazio per avere un nuovo elenco, sostituire i vecchi dati con i nuovi dati e contrassegnare il vecchi dati per la garbage collection perché non fanno più riferimento a nulla, quindi dire che sta funzionando sul posto è un po 'allungando il concetto rispetto a quello che ho mostrato è possibile ... è inefficiente? si, ma l'ho detto in anticipo.
gboffi,

0

L'approccio di zmk utilizza la comprensione dell'elenco che è molto veloce, ma mantiene l'ordine naturalmente. Per l'applicazione a stringhe maiuscole e minuscole può essere facilmente modificato. Ciò preserva anche la custodia originale.

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

Le funzioni strettamente associate sono:

def HasDupes(aseq) :
    s = set()
    return any(((x.lower() in s) or s.add(x.lower())) for x in aseq)

def GetDupes(aseq) :
    s = set()
    return set(x for x in aseq if ((x.lower() in s) or s.add(x.lower())))

0

Una comprensione dell'elenco di copertina:

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

Basta aggiungere un condizionale per verificare che il valore non sia su una posizione precedente

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.