Angenommen, die Liste ['one', 'two', 'one']
sollte der Algorithmus True
zurückgeben, während ['one', 'two', 'three']
False
zurückgegeben werden sollte.
Verwenden Sie set()
, um Duplikate zu entfernen, wenn alle Werte hashable sind:
>>> your_list = ['one', 'two', 'one']
>>> len(your_list) != len(set(your_list))
True
Nur für short -Listen empfohlen:
any(thelist.count(x) > 1 for x in thelist)
Verwenden Sie nicht für eine lange Liste - es kann eine Zeit dauern, die proportional zum Quadrat der Anzahl der Elemente in der Liste ist!
Für längere Listen mit Hash-Elementen (Zeichenfolgen, Zahlen usw.):
def anydup(thelist):
seen = set()
for x in thelist:
if x in seen: return True
seen.add(x)
return False
Wenn Ihre Artikel nicht hashierbar sind (Unterlisten, Diktate usw.), werden sie haariger, obwohl es immer noch möglich ist, O (N logN) zu erhalten, wenn sie zumindest vergleichbar sind. Um die bestmögliche Leistung zu erzielen, müssen Sie jedoch die Eigenschaften der Elemente kennen oder testen (hashierbar oder nicht, vergleichbar oder nicht) - O(N) für Hashwerte, O (N log N) für Vergleichbare hashable, ansonsten liegt es an O (N im Quadrat) und es gibt nichts, was man dagegen tun kann :-(.
Dies ist alt, aber die Antworten hier führten mich zu einer etwas anderen Lösung. Wenn Sie Verständniss missbrauchen möchten, können Sie auf diese Weise einen Kurzschluss bekommen.
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))
Wenn Sie einen funktionalen Programmierstil bevorzugen, finden Sie hier eine nützliche Funktion, selbstdokumentierten und getesteten Code mit 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 __== "__main__":
import doctest
doctest.testmod()
Von dort aus können Sie Unicity testen, indem Sie prüfen, ob das zweite Element des zurückgegebenen Paares leer ist:
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]
Beachten Sie, dass dies nicht effizient ist, da Sie die Zerlegung explizit erstellen. Entlang der Verwendung von Reduzieren können Sie jedoch etwas Äquivalentes (aber etwas weniger effizient) zur Beantwortung von Punkt 5 erreichen:
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
Ich habe vor kurzem eine verwandte Frage beantwortet, um alle Duplikate zu erstellen in einer Liste mit einem Generator. Es hat den Vorteil, dass, wenn es nur verwendet wird, um festzustellen, ob es ein Duplikat gibt, das erste Element abgerufen werden muss und der Rest ignoriert werden kann. Dies ist die ultimative Abkürzung.
Dies ist ein interessanter satzbasierter Ansatz, den ich direkt aus moooeeeep angepasst habe:
def getDupes(l):
seen = set()
seen_add = seen.add
for x in l:
if x in seen or seen_add(x):
yield x
Dementsprechend wäre eine vollständige Liste von Dup-Funktionen list(getDupes(etc))
. Um einfach zu testen, "wenn" es ein Dupe gibt, sollte es wie folgt verpackt werden:
def hasDupes(l):
try:
if getDupes(c).next(): return True # Found a dupe
except StopIteration:
pass
return False
Dies lässt sich gut skalieren und sorgt für konsistente Betriebszeiten überall dort, wo Dupe in der Liste enthalten ist. Ich habe es mit Listen von bis zu 1m Einträgen getestet. Wenn Sie etwas über die Daten wissen, insbesondere, dass Dupes wahrscheinlich in der ersten Hälfte auftauchen, oder andere Dinge, mit denen Sie Ihre Anforderungen verzerren können, beispielsweise die tatsächlichen Dupes, dann gibt es ein paar wirklich alternative Dup-Locators das könnte sich übertreffen. Die zwei, die ich empfehle, sind ...
Einfacher dict-basierter Ansatz, sehr lesbar:
def getDupes(c):
d = {}
for i in c:
if i in d:
if d[i]:
yield i
d[i] = False
else:
d[i] = True
Nutzen Sie itertools (im Wesentlichen ein ifilter/izip/tee) auf der sortierten Liste. Sehr effizient, wenn Sie alle Dupes erhalten, wenn auch nicht so schnell, um nur die erste zu erhalten:
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
Dies waren die Top-Performer aus den Ansätzen, die ich für die Full-Dupe-Liste ausprobiert habe, wobei der erste Dupe an einer beliebigen Stelle in einer 1-m-Elementliste vom Anfang bis zur Mitte auftrat. Es war überraschend, wie wenig Aufwand der Sortierschritt hinzufügte. Ihre Laufleistung kann variieren, aber hier sind meine spezifischen zeitlichen Ergebnisse:
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
Eine andere Möglichkeit, dies auf den Punkt zu bringen, ist mit Counter .
So stellen Sie fest, ob in der ursprünglichen Liste Duplikate vorhanden sind:
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
Oder Sie erhalten eine Liste der Elemente, die Duplikate enthalten:
def get_dupes(l):
return [k for k, v in Counter(l).items() if v > 1]
Ich dachte, es wäre nützlich, die Zeitabläufe der verschiedenen hier vorgestellten Lösungen zu vergleichen. Dafür habe ich meine eigene Bibliothek benutzt simple_benchmark
:
Also in der Tat ist für diesen Fall die Lösung von Denis Otkidach am schnellsten.
Einige der Ansätze weisen auch eine viel steilere Kurve auf. Diese Ansätze skalieren quadratisch mit der Anzahl der Elemente (Alex Martellis erste Lösung, wjandrea und beide Xavier Decorets-Lösungen). Ebenfalls wichtig zu erwähnen ist, dass die pandas -Lösung von Keiku einen sehr großen konstanten Faktor hat. Bei größeren Listen holt es die anderen Lösungen fast ein.
Und falls das Duplikat an erster Stelle steht. Dies ist nützlich, um zu sehen, welche Lösungen kurzschließen:
Hier schließen mehrere Ansätze nicht kurz: Kaiku, Frank, Xavier_Decoret (erste Lösung), Turn, Alex Martelli (erste Lösung) und der Ansatz von Denis Otkidach (der im Fall ohne Duplizieren am schnellsten war).
Ich habe hier eine Funktion aus meiner eigenen Bibliothek eingefügt: iteration_utilities.all_distinct
die mit der schnellsten Lösung im Fall ohne Duplikate konkurrieren kann und in konstanter Zeit für den Fall mit Duplikat am Anfang ausgeführt wird ( obwohl nicht so schnell).
Der Code für den 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)
_
Und für die Argumente:
_
# 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()
_
Ich habe festgestellt, dass dies die beste Leistung bringt, weil die Operation beim ersten Duplizieren kurzgeschlossen wurde. Dann hat dieser Algorithmus Zeit- und Raumkomplexität O(n).
def has_duplicated_elements(self, 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
Der Einfachheit halber habe ich den Ansatz von pyrospade verwendet und diesen in einer kurzen Liste aus der Windows-Registrierung ohne Berücksichtigung der Groß- und Kleinschreibung geringfügig geändert.
Wenn die unformatierte PATH-Wertzeichenfolge in einzelne Pfade aufgeteilt ist, können alle Nullpfade (leere oder nur mit Leerzeichen versehene Zeichenfolgen) entfernt werden, indem Folgendes verwendet wird:
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()))]
Der ursprüngliche Pfad enthält zu Testzwecken sowohl Null-Einträge als auch Duplikate:
[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
Nullpfade wurden entfernt, es sind jedoch immer noch Duplikate vorhanden, z. B. (1, 3) und (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\
Und zum Schluss wurden die Dupes entfernt:
[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\
Wenn die Liste nicht verarbeitbare Elemente enthält, können Sie Alex Martellis Lösung verwenden, jedoch mit einer Liste anstelle einer Menge, obwohl dies für größere Eingaben langsamer ist: O (N ^ 2).
def has_duplicates(iterable):
seen = []
for x in iterable:
if x in seen:
return True
seen.append(x)
return False
Ich weiß nicht genau, was das Set hinter den Kulissen macht, deshalb halte ich es einfach, es einfach zu halten.
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
Eine einfachere Lösung sieht wie folgt aus. Prüfen Sie einfach mit der Pandas .duplicated()
Methode True/False und nehmen Sie dann die Summe. Siehe auch pandas.Series.duplicated - pandas 0.24.1 Dokumentation
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