web-dev-qa-db-de.com

Wie überprüfe ich, ob Duplikate in einer Wohnungsliste enthalten sind?

Angenommen, die Liste ['one', 'two', 'one'] sollte der Algorithmus True zurückgeben, während ['one', 'two', 'three']False zurückgegeben werden sollte.

137
teggy

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
312
Denis Otkidach

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 :-(.

40
Alex Martelli

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))
10
pyrospade

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
10
Xavier Decoret

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
5
F1Rumors

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]
3
Turn

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 :

enter image description here

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:

enter image description here

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()
_
2
MSeifert

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
1
user

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\
0
Hewey Dewey

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
0
wjandrea

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
0
Frank

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
0
Keiku