Come posso verificare se ci sono duplicati in un elenco piatto?


185

Ad esempio, dato l'elenco ['one', 'two', 'one'], l'algoritmo dovrebbe tornare True, mentre dato ['one', 'two', 'three']dovrebbe tornare False.

Risposte:


399

Utilizzare set()per rimuovere i duplicati se tutti i valori sono hash :

>>> your_list = ['one', 'two', 'one']
>>> len(your_list) != len(set(your_list))
True

17
Prima di leggere questo ho provato la tua_elenco! = Lista (set (tua_elenco)) che non funzionerà poiché l'ordine degli elementi cambierà. Usare len è un buon modo per risolvere questo problema
igniteflow

1
spesso non funziona per array di galleggiamento points.See stackoverflow.com/questions/60914705
Manas Dogra

54

Consigliato solo per elenchi brevi :

any(thelist.count(x) > 1 for x in thelist)

Evitare Non utilizzare su una lunga lista - si può prendere tempo proporzionale alla piazza del numero di elementi nella lista!

Per elenchi più lunghi con elementi cancellabili (stringhe, numeri, ecc.):

def anydup(thelist):
  seen = set()
  for x in thelist:
    if x in seen: return True
    seen.add(x)
  return False

Se i tuoi articoli non sono cancellabili (elenchi, dicts, ecc.) Diventa più peloso, anche se potrebbe essere ancora possibile ottenere O (N logN) se almeno sono comparabili. Ma devi conoscere o testare le caratteristiche degli oggetti (lavabili o no, confrontabili o no) per ottenere le migliori prestazioni possibili - O (N) per gli hashble, O (N log N) per gli oggetti comparabili non hash, altrimenti dipende da O (N al quadrato) e non c'è nulla che si possa fare al riguardo :-(.


21
Denis Otkidach ha offerto una soluzione in cui è sufficiente creare un nuovo set dall'elenco, quindi verificarne la lunghezza. Il suo vantaggio è che sta lasciando che il codice C all'interno di Python esegua il lavoro pesante. La tua soluzione esegue il loop nel codice Python, ma ha il vantaggio di cortocircuitare quando viene trovata una singola corrispondenza. Se le probabilità sono che l'elenco probabilmente non ha duplicati, mi piace la versione di Denis Otkidach, ma se le probabilità sono che potrebbe esserci un duplicato all'inizio dell'elenco, questa soluzione è migliore.
Steveha,

1
Vale la pena dare un'occhiata ai dettagli, anche se penso che Denis avesse la soluzione più ordinata.
Steve314,

@steveha - ottimizzazione prematura?
Steve314,

@ Steve314, quale ottimizzazione prematura? Lo avrei scritto nel modo in cui Denis Otkidach lo ha scritto, quindi stavo cercando di capire perché Alex Martelli (della fama di Python Cookbook) lo ha scritto in modo diverso. Dopo averci pensato un po 'mi sono reso conto che la versione di Alex era in cortocircuito e ho pubblicato alcuni pensieri sulle differenze. Come passi dalla discussione sulle differenze all'ottimizzazione prematura, la radice di tutto il male?
Steveha,

3
Se gli oggetti sono cancellabili, una soluzione impostata è più diretta e, nel modo in cui l'ho espressa, più veloce (esce non appena si conosce la risposta - "cortocircuiti", Steveha l'ha messa). Costruire il dict che proponi (più veloce come una raccolta. Contatore) è ovviamente molto più lento (richiede un numero di allconteggi 1). Un dict con tutti i valori True, che citi anche tu, è un imitazione ridicolmente, inutilmente gonfio di un set, senza alcun valore aggiunto. Big-O non è tutto in programmazione.
Alex Martelli,

12

Questo è vecchio, ma le risposte qui mi hanno portato a una soluzione leggermente diversa. Se sei disposto ad abusare delle comprensioni, puoi ottenere un corto circuito in questo modo.

xs = [1, 2, 1]
s = set()
any(x in s or s.add(x) for x in xs)
# You can use a similar approach to actually retrieve the duplicates.
s = set()
duplicates = set(x for x in xs if x in s or s.add(x))

9

Se sei appassionato di stile di programmazione funzionale, ecco una funzione utile, un codice auto-documentato e testato che utilizza doctest .

def decompose(a_list):
    """Turns a list into a set of all elements and a set of duplicated elements.

    Returns a pair of sets. The first one contains elements
    that are found at least once in the list. The second one
    contains elements that appear more than once.

    >>> decompose([1,2,3,5,3,2,6])
    (set([1, 2, 3, 5, 6]), set([2, 3]))
    """
    return reduce(
        lambda (u, d), o : (u.union([o]), d.union(u.intersection([o]))),
        a_list,
        (set(), set()))

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Da lì puoi testare l'unicity controllando se il secondo elemento della coppia restituita è vuoto:

def is_set(l):
    """Test if there is no duplicate element in l.

    >>> is_set([1,2,3])
    True
    >>> is_set([1,2,1])
    False
    >>> is_set([])
    True
    """
    return not decompose(l)[1]

Si noti che ciò non è efficace poiché si sta costruendo esplicitamente la decomposizione. Ma lungo la linea di utilizzo di ridurre, puoi arrivare a qualcosa di equivalente (ma leggermente meno efficiente) per rispondere a 5:

def is_set(l):
    try:
        def func(s, o):
            if o in s:
                raise Exception
            return s.union([o])
        reduce(func, l, set())
        return True
    except:
        return False

Avrei dovuto leggere prima le domande correlate. Questo è descritto in stackoverflow.com/questions/1723072/...
Xavier Decoret

1
Mi genera un errore di "sintassi non valida" sulla funzione lambda di decompose ()
raffaem

Questo perché la decompressione negli elenchi di argomenti lambda è stata rimossa in Python 3.x.
MSeifert,

5

Ho pensato che sarebbe stato utile confrontare i tempi delle diverse soluzioni presentate qui. Per questo ho usato la mia biblioteca simple_benchmark:

inserisci qui la descrizione dell'immagine

Quindi, in questo caso, la soluzione di Denis Otkidach è la più veloce.

Alcuni degli approcci mostrano anche una curva molto più ripida, questi sono gli approcci che scalano in scala quadratica con il numero di elementi (prima soluzione di Alex Martellis, wjandrea ed entrambe le soluzioni di Xavier Decorets). È anche importante ricordare che la soluzione Panda di Keiku ha un fattore costante molto grande. Ma per elenchi più grandi si avvicina quasi alle altre soluzioni.

E nel caso in cui il duplicato si trovi nella prima posizione. Questo è utile per vedere quali soluzioni sono in corto circuito:

inserisci qui la descrizione dell'immagine

Qui diversi approcci non vanno in corto circuito: Kaiku, Frank, Xavier_Decoret (prima soluzione), Turn, Alex Martelli (prima soluzione) e l'approccio presentato da Denis Otkidach (che è stato il più veloce nel caso del no-duplicato).

Ho incluso una funzione dalla mia libreria qui: iteration_utilities.all_distinctche può competere con la soluzione più veloce nel caso di non duplicati e si esibisce in tempo costante per il caso di duplicato all'inizio (anche se non come il più veloce).

Il codice per il benchmark:

from collections import Counter
from functools import reduce

import pandas as pd
from simple_benchmark import BenchmarkBuilder
from iteration_utilities import all_distinct

b = BenchmarkBuilder()

@b.add_function()
def Keiku(l):
    return pd.Series(l).duplicated().sum() > 0

@b.add_function()
def Frank(num_list):
    unique = []
    dupes = []
    for i in num_list:
        if i not in unique:
            unique.append(i)
        else:
            dupes.append(i)
    if len(dupes) != 0:
        return False
    else:
        return True

@b.add_function()
def wjandrea(iterable):
    seen = []
    for x in iterable:
        if x in seen:
            return True
        seen.append(x)
    return False

@b.add_function()
def user(iterable):
    clean_elements_set = set()
    clean_elements_set_add = clean_elements_set.add

    for possible_duplicate_element in iterable:

        if possible_duplicate_element in clean_elements_set:
            return True

        else:
            clean_elements_set_add( possible_duplicate_element )

    return False

@b.add_function()
def Turn(l):
    return Counter(l).most_common()[0][1] > 1

def getDupes(l):
    seen = set()
    seen_add = seen.add
    for x in l:
        if x in seen or seen_add(x):
            yield x

@b.add_function()          
def F1Rumors(l):
    try:
        if next(getDupes(l)): return True    # Found a dupe
    except StopIteration:
        pass
    return False

def decompose(a_list):
    return reduce(
        lambda u, o : (u[0].union([o]), u[1].union(u[0].intersection([o]))),
        a_list,
        (set(), set()))

@b.add_function()
def Xavier_Decoret_1(l):
    return not decompose(l)[1]

@b.add_function()
def Xavier_Decoret_2(l):
    try:
        def func(s, o):
            if o in s:
                raise Exception
            return s.union([o])
        reduce(func, l, set())
        return True
    except:
        return False

@b.add_function()
def pyrospade(xs):
    s = set()
    return any(x in s or s.add(x) for x in xs)

@b.add_function()
def Alex_Martelli_1(thelist):
    return any(thelist.count(x) > 1 for x in thelist)

@b.add_function()
def Alex_Martelli_2(thelist):
    seen = set()
    for x in thelist:
        if x in seen: return True
        seen.add(x)
    return False

@b.add_function()
def Denis_Otkidach(your_list):
    return len(your_list) != len(set(your_list))

@b.add_function()
def MSeifert04(l):
    return not all_distinct(l)

E per gli argomenti:


# No duplicate run
@b.add_arguments('list size')
def arguments():
    for exp in range(2, 14):
        size = 2**exp
        yield size, list(range(size))

# Duplicate at beginning run
@b.add_arguments('list size')
def arguments():
    for exp in range(2, 14):
        size = 2**exp
        yield size, [0, *list(range(size)]

# Running and plotting
r = b.run()
r.plot()

Per riferimento: la funzione all_distinct è scritto in C .
utente

5

Di recente ho risposto a una domanda correlata per stabilire tutti i duplicati in un elenco, utilizzando un generatore. Ha il vantaggio che se usato solo per stabilire "se c'è un duplicato", devi solo ottenere il primo oggetto e il resto può essere ignorato, che è l'ultimo collegamento.

Questo è un approccio basato sul set interessante che ho adattato direttamente da Moooeeeep :

def getDupes(l):
    seen = set()
    seen_add = seen.add
    for x in l:
        if x in seen or seen_add(x):
            yield x

Di conseguenza, sarebbe un elenco completo di duplicati list(getDupes(etc)). Per testare semplicemente "if" c'è un duplicato, dovrebbe essere racchiuso come segue:

def hasDupes(l):
    try:
        if getDupes(l).next(): return True    # Found a dupe
    except StopIteration:
        pass
    return False

Questo si adatta bene e fornisce tempi di funzionamento coerenti ovunque il duplicato sia nell'elenco - ho testato con elenchi fino a 1 milione di voci. Se sai qualcosa sui dati, in particolare, è probabile che i duplicati vengano visualizzati nella prima metà o altre cose che ti consentano di distorcere i tuoi requisiti, come la necessità di ottenere i duplicati effettivi, allora ci sono un paio di localizzatori di duplicati davvero alternativi che potrebbe sovraperformare. I due che raccomando sono ...

Approccio basato su dict semplice, molto leggibile:

def getDupes(c):
    d = {}
    for i in c:
        if i in d:
            if d[i]:
                yield i
                d[i] = False
        else:
            d[i] = True

Sfrutta itertools (essenzialmente un ifilter / izip / tee) nell'elenco ordinato, molto efficace se stai ottenendo tutti i duplicati anche se non così veloce da ottenere solo il primo:

def getDupes(c):
    a, b = itertools.tee(sorted(c))
    next(b, None)
    r = None
    for k, g in itertools.ifilter(lambda x: x[0]==x[1], itertools.izip(a, b)):
        if k != r:
            yield k
            r = k

Questi sono stati i migliori esecutori dagli approcci che ho provato per l' elenco full dupe , con il primo dupe che si verifica ovunque in un elenco di elementi di 1 m dall'inizio alla metà. È stato sorprendente quanto poco sia stato aggiunto il passaggio di ordinamento. Il tuo chilometraggio può variare, ma ecco i miei risultati specifici a tempo:

Finding FIRST duplicate, single dupe places "n" elements in to 1m element array

Test set len change :        50 -  . . . . .  -- 0.002
Test in dict        :        50 -  . . . . .  -- 0.002
Test in set         :        50 -  . . . . .  -- 0.002
Test sort/adjacent  :        50 -  . . . . .  -- 0.023
Test sort/groupby   :        50 -  . . . . .  -- 0.026
Test sort/zip       :        50 -  . . . . .  -- 1.102
Test sort/izip      :        50 -  . . . . .  -- 0.035
Test sort/tee/izip  :        50 -  . . . . .  -- 0.024
Test moooeeeep      :        50 -  . . . . .  -- 0.001 *
Test iter*/sorted   :        50 -  . . . . .  -- 0.027

Test set len change :      5000 -  . . . . .  -- 0.017
Test in dict        :      5000 -  . . . . .  -- 0.003 *
Test in set         :      5000 -  . . . . .  -- 0.004
Test sort/adjacent  :      5000 -  . . . . .  -- 0.031
Test sort/groupby   :      5000 -  . . . . .  -- 0.035
Test sort/zip       :      5000 -  . . . . .  -- 1.080
Test sort/izip      :      5000 -  . . . . .  -- 0.043
Test sort/tee/izip  :      5000 -  . . . . .  -- 0.031
Test moooeeeep      :      5000 -  . . . . .  -- 0.003 *
Test iter*/sorted   :      5000 -  . . . . .  -- 0.031

Test set len change :     50000 -  . . . . .  -- 0.035
Test in dict        :     50000 -  . . . . .  -- 0.023
Test in set         :     50000 -  . . . . .  -- 0.023
Test sort/adjacent  :     50000 -  . . . . .  -- 0.036
Test sort/groupby   :     50000 -  . . . . .  -- 0.134
Test sort/zip       :     50000 -  . . . . .  -- 1.121
Test sort/izip      :     50000 -  . . . . .  -- 0.054
Test sort/tee/izip  :     50000 -  . . . . .  -- 0.045
Test moooeeeep      :     50000 -  . . . . .  -- 0.019 *
Test iter*/sorted   :     50000 -  . . . . .  -- 0.055

Test set len change :    500000 -  . . . . .  -- 0.249
Test in dict        :    500000 -  . . . . .  -- 0.145
Test in set         :    500000 -  . . . . .  -- 0.165
Test sort/adjacent  :    500000 -  . . . . .  -- 0.139
Test sort/groupby   :    500000 -  . . . . .  -- 1.138
Test sort/zip       :    500000 -  . . . . .  -- 1.159
Test sort/izip      :    500000 -  . . . . .  -- 0.126
Test sort/tee/izip  :    500000 -  . . . . .  -- 0.120 *
Test moooeeeep      :    500000 -  . . . . .  -- 0.131
Test iter*/sorted   :    500000 -  . . . . .  -- 0.157

La .next()chiamata nel secondo blocco di codice non funziona su Python 3.x. Penso che next(getDupes(l))dovrebbe funzionare su tutte le versioni di Python, quindi potrebbe avere senso cambiarlo.
MSeifert,

Inoltre ifiltere ìzippuò essere semplicemente sostituito dal built-in filtere zipin Python 3.x.
MSeifert,

@MSeifert la soluzione funziona per python 2.x come scritto, e sì, per py3 puoi usare il filtro e mappare direttamente ... ma qualcuno che usa la soluzione py3 nella base di codice py2 non otterrebbe i benefici perché non funzionerebbe come un Generatore. Esplicito è meglio che implicito in questo caso;)
F1Rumors

3

Un altro modo per farlo in modo succinto è con Counter .

Per determinare se ci sono duplicati nell'elenco originale:

from collections import Counter

def has_dupes(l):
    # second element of the tuple has number of repetitions
    return Counter(l).most_common()[0][1] > 1

Oppure per ottenere un elenco di elementi con duplicati:

def get_dupes(l):
    return [k for k, v in Counter(l).items() if v > 1]

2
my_list = ['one', 'two', 'one']

duplicates = []

for value in my_list:
  if my_list.count(value) > 1:
    if value not in duplicates:
      duplicates.append(value)

print(duplicates) //["one"]

1

Ho trovato questo per fare le migliori prestazioni perché cortocircuita l'operazione quando il primo duplicato ha trovato, quindi questo algoritmo ha complessità di tempo e spazio O (n) dove n è la lunghezza della lista:

def has_duplicated_elements(iterable):
    """ Given an `iterable`, return True if there are duplicated entries. """
    clean_elements_set = set()
    clean_elements_set_add = clean_elements_set.add

    for possible_duplicate_element in iterable:

        if possible_duplicate_element in clean_elements_set:
            return True

        else:
            clean_elements_set_add( possible_duplicate_element )

    return False

0

Non so davvero cosa set dietro le quinte, quindi mi piace semplicemente mantenerlo semplice.

def dupes(num_list):
    unique = []
    dupes = []
    for i in num_list:
        if i not in unique:
            unique.append(i)
        else:
            dupes.append(i)
    if len(dupes) != 0:
        return False
    else:
        return True

0

Una soluzione più semplice è la seguente. Basta controllare Vero / Falso con il .duplicated()metodo Panda e quindi prendere la somma. Vedere anche la documentazione di pandas.Series.duplicated - pandas 0.24.1

import pandas as pd

def has_duplicated(l):
    return pd.Series(l).duplicated().sum() > 0

print(has_duplicated(['one', 'two', 'one']))
# True
print(has_duplicated(['one', 'two', 'three']))
# False

0

Se l'elenco contiene elementi non lavabili, puoi utilizzare la soluzione di Alex Martelli ma con un elenco anziché un set, sebbene sia più lento per input più grandi: O (N ^ 2).

def has_duplicates(iterable):
    seen = []
    for x in iterable:
        if x in seen:
            return True
        seen.append(x)
    return False

0

Ho usato l'approccio di pyrospade, per la sua semplicità, e l'ho modificato leggermente in un breve elenco creato dal registro di Windows senza distinzione tra maiuscole e minuscole.

Se la stringa di valore PATH non elaborata viene suddivisa in percorsi individuali, è possibile rimuovere tutti i percorsi 'null' (stringhe vuote o solo per gli spazi bianchi) utilizzando:

PATH_nonulls = [s for s in PATH if s.strip()]

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())))

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

Il PERCORSO originale ha sia voci "nulle" che duplicati a scopo di test:

[list]  Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH[list]  Root paths in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
  1  C:\Python37\
  2
  3
  4  C:\Python37\Scripts\
  5  c:\python37\
  6  C:\Program Files\ImageMagick-7.0.8-Q8
  7  C:\Program Files (x86)\poppler\bin
  8  D:\DATA\Sounds
  9  C:\Program Files (x86)\GnuWin32\bin
 10  C:\Program Files (x86)\Intel\iCLS Client\
 11  C:\Program Files\Intel\iCLS Client\
 12  D:\DATA\CCMD\FF
 13  D:\DATA\CCMD
 14  D:\DATA\UTIL
 15  C:\
 16  D:\DATA\UHELP
 17  %SystemRoot%\system32
 18
 19
 20  D:\DATA\CCMD\FF%SystemRoot%
 21  D:\DATA\Sounds
 22  %SystemRoot%\System32\Wbem
 23  D:\DATA\CCMD\FF
 24
 25
 26  c:\
 27  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\
 28

I percorsi null sono stati rimossi, ma hanno ancora dei duplicati, ad esempio (1, 3) e (13, 20):

    [list]  Null paths removed from HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
  1  C:\Python37\
  2  C:\Python37\Scripts\
  3  c:\python37\
  4  C:\Program Files\ImageMagick-7.0.8-Q8
  5  C:\Program Files (x86)\poppler\bin
  6  D:\DATA\Sounds
  7  C:\Program Files (x86)\GnuWin32\bin
  8  C:\Program Files (x86)\Intel\iCLS Client\
  9  C:\Program Files\Intel\iCLS Client\
 10  D:\DATA\CCMD\FF
 11  D:\DATA\CCMD
 12  D:\DATA\UTIL
 13  C:\
 14  D:\DATA\UHELP
 15  %SystemRoot%\system32
 16  D:\DATA\CCMD\FF%SystemRoot%
 17  D:\DATA\Sounds
 18  %SystemRoot%\System32\Wbem
 19  D:\DATA\CCMD\FF
 20  c:\
 21  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\

E infine, i duplicati sono stati rimossi:

[list]  Massaged path list from in HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH
  1  C:\Python37\
  2  C:\Python37\Scripts\
  3  C:\Program Files\ImageMagick-7.0.8-Q8
  4  C:\Program Files (x86)\poppler\bin
  5  D:\DATA\Sounds
  6  C:\Program Files (x86)\GnuWin32\bin
  7  C:\Program Files (x86)\Intel\iCLS Client\
  8  C:\Program Files\Intel\iCLS Client\
  9  D:\DATA\CCMD\FF
 10  D:\DATA\CCMD
 11  D:\DATA\UTIL
 12  C:\
 13  D:\DATA\UHELP
 14  %SystemRoot%\system32
 15  D:\DATA\CCMD\FF%SystemRoot%
 16  %SystemRoot%\System32\Wbem
 17  %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\

0
def check_duplicates(my_list):
    seen = {}
    for item in my_list:
        if seen.get(item):
            return True
        seen[item] = True
    return False

Come funziona la funzione? Sono curioso di sapere come viene popolato il dizionario "visto".
montagna
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.