Як перевірити, чи є дублікати в плоскому списку?


185

Наприклад, з урахуванням списку ['one', 'two', 'one']алгоритм повинен повернутися True, тоді як, якщо ['one', 'two', 'three']він буде повернутий False.

Відповіді:


398

Використовуйте set()для видалення дублікатів, якщо всі значення є доступними :

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

17
Перш ніж прочитати це, я спробував your_list! = List (set (your_list)), який не працюватиме, оскільки порядок елементів зміниться. Використання len - це хороший спосіб вирішити цю проблему
igniteflow

1
часто не працює масив з плаваючою points.See stackoverflow.com/questions/60914705
Манас догрів

54

Рекомендовано лише для коротких списків:

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

Ви НЕ використовувати в довгому списку - це може зайняти деякий час , пропорційне квадрату числа елементів в списку!

Для більш тривалих списків з елементами, що переміщуються (рядки, числа та с):

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

Якщо ваші предмети не доступні для перегляду (підсписки, дикти та ін.), Вони набувають перешкод, хоча все-таки можливо отримати O (N logN), якщо вони принаймні порівнянні. Але вам потрібно знати або перевірити характеристики елементів (хешируемих чи ні, порівнянні чи ні), щоб отримати найкращі показники - O (N) для хешбелів, O (N log N) для непридатних порівнянь, інакше це вниз до O (N квадрата), і ніхто нічого з цим не може зробити :-(


21
Денис Откідач запропонував рішення, де ви просто зібрати новий список зі списку, а потім перевірити його довжину. Його перевага полягає в тому, що він дозволяє C-коду всередині Python робити важкий підйом. Ваше рішення петляється в коді Python, але має перевагу короткого замикання, коли знайдено єдину відповідність. Якщо шанси полягають у тому, що у списку, ймовірно, немає дублікатів, мені подобається версія Дениса Откідача, але якщо шанси на те, що на початку цього списку може бути дублікат, це краще рішення.
steveha

1
Варто до деталей, хоча я думаю, що Денис мав більш акуратне рішення.
Стів314

@steveha - передчасна оптимізація?
Steve314

@ Steve314, яка передчасна оптимізація? Я написав би це так, як написав Денис Откідач, тому я намагався зрозуміти, чому Алекс Мартеллі (слава Кухарської книги Python) написав це по-різному. Після того, як я трохи подумав над цим, я зрозумів, що версія Алекса коротке замикання, і я розмістив кілька думок про відмінності. Як ви переходите від обговорення відмінностей до передчасної оптимізації, корінь всього зла?
steveha

3
Якщо елементи є хешируемими, набір рішення є більш прямим, і, як я це висловив, швидше (виходить, як тільки відповідь буде відома - "коротке замикання", Steveha поставив це). Збірка запропонованої вами картини (найшвидше, як колекція. Контроль), звичайно, набагато повільніша (потребує того, що allналічує все, що 1). Диктант з усіма значеннями True, про який ви також згадуєте, - це смішна, марно мітусна мимохімія set, без жодної додаткової вартості. Big-O - це не все в програмуванні.
Алекс Мартеллі

12

Це старе, але відповіді тут привели мене до трохи іншого рішення. Якщо ви готові до зловживань із розумінням, ви можете отримати коротке замикання таким чином.

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

Якщо ви любите функціональний стиль програмування, ось корисна функція, самодокументований і перевірений код за допомогою 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()

Звідти ви можете перевірити єдиність, перевіривши, чи другий елемент повернутої пари порожній:

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]

Зауважте, що це не ефективно, оскільки ви чітко будуєте декомпозицію. Але по лінії використання скорочення ви можете знайти щось еквівалентне (але трохи менш ефективне), щоб відповісти 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

Слід спочатку прочитати відповідні запитання. Про це йдеться в stackoverflow.com/questions/1723072/…
Xavier Decoret

1
Це кидає мені помилку "недійсного синтаксису" на лямбда-функцію декомпозиції ()
raffaem

Це тому, що розпакування в списках аргументів лямбда видалено в Python 3.x.
MSeifert

5

Я вважав, що було б корисно порівняти терміни різних рішень, представлених тут. Для цього я використовував власну бібліотеку simple_benchmark:

введіть тут опис зображення

Тож справді для цього випадку рішення Дениса Откідача є найшвидшим.

Деякі з підходів також демонструють набагато більш круту криву, це підходи, які масштабують квадратично з кількістю елементів (перше рішення Алекса Мартелліса, wjandrea і обидва рішення Xavier Decorets). Також важливо зазначити, що рішення панди від Keiku має дуже великий постійний фактор. Але для більш великих списків він майже наздоганяє інші рішення.

І на випадок, якщо дублікат знаходиться на першій позиції. Це корисно, щоб побачити, які рішення мають коротке замикання:

введіть тут опис зображення

Тут кілька підходів не мають короткого замикання: Kaiku, Frank, Xavier_Decoret (перше рішення), Turn, Алекс Мартеллі (перше рішення) та підхід, представлений Денисом Откідачем (який був найшвидшим у справі, що не повторюється).

Сюди я включив функцію зі своєї власної бібліотеки: iteration_utilities.all_distinctяка може конкурувати з найшвидшим рішенням у випадку без дублікатів і виконує в постійному часі для випадку дублікат на початку (хоча і не найшвидший).

Код для еталону:

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)

А для аргументів:


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

Для довідки: all_distinct функція написана на C .
користувач

5

Нещодавно я відповів на пов'язане питання, щоб встановити всі дублікати у списку, використовуючи генератор. Перевагою є те, що якщо використовується лише для встановлення "якщо є дублікат", вам потрібно отримати перший елемент, а решта можна проігнорувати, що є остаточним ярликом.

Це цікавий підхід на основі набору, який я адаптував прямо з moooeeeep :

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

Відповідно, був би повний перелік гномів list(getDupes(etc)). Щоб просто перевірити "якщо" є дупа, її слід загорнути так:

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

Це добре розширює масштаби та забезпечує послідовний час роботи, де в списку є дупа - я тестував зі списками до 1 м записів. Якщо ви щось знаєте про дані, зокрема, що вірогідність появи гномів з’явиться в першій половині чи інші речі, які дозволять вам перекосити ваші вимоги, як, наприклад, необхідність отримати фактичні мандарини, то є кілька справді альтернативних локаторів дуп. що може перевершити. Я рекомендую два ...

Простий підхід на основі диктату, дуже зрозумілий:

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

Використовуйте itertools (по суті, це ifilter / izip / tee) у відсортованому списку, дуже ефективний, якщо ви отримуєте всі пустушки, хоча і не так швидко, щоб отримати лише перший:

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

Це були найкращі виконавці з підходів, які я пробував для повного списку дупів , причому перший дуп з'явився де-небудь у списку елементів 1м від початку до середини. Було дивно, наскільки мало накладних кроків сортування. Ваш пробіг може відрізнятися, але ось мої конкретні часові результати:

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

.next()Виклику в вашому другому блоці коду не працює на Python 3.x. Я думаю, що next(getDupes(l))слід працювати в будь-яких версіях Python, тому це може мати сенс змінити.
MSeifert

Крім того, ifilterі ìzipможе бути просто замінена вбудованої filterі zipв Python 3.x.
MSeifert

@MSeifert рішення працює для python 2.x, як написано, і так, для py3 ви можете використовувати фільтр та карту безпосередньо ... але хтось, хто використовує рішення py3 в базі коду py2, не отримає переваг, оскільки він не працюватиме як генератор. Явне в цьому випадку краще, ніж неявне;)
F1Rumors

3

Ще один спосіб зробити це лаконічно - за допомогою Counter .

Щоб просто визначити, чи є дублікати у вихідному списку:

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

Або щоб отримати список елементів, які мають дублікати:

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

Я виявив, що це робить найкращу ефективність, оскільки він коротко замикає роботу, коли перший дублікат знайшов, тоді цей алгоритм має складність у часі та просторі O (n), де n - довжина списку:

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

Я дійсно не знаю, який набір робить за лаштунками, тому мені просто подобається робити це просто.

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

Більш просте рішення полягає в наступному. Просто перевірте значення True / False .duplicated()методом pandas, а потім візьміть суму. Також дивіться документацію на pandas.Series.duplicate - 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

Якщо список містить незмінні елементи, ви можете використовувати рішення Алекса Мартеллі, але зі списком замість набору, хоча для великих входів це повільніше: O (N ^ 2).

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

0

Я застосував підхід pyrospade для його простоти і трохи змінив його в короткому списку, складеному з нечутливого до регістру Windows реєстру.

Якщо необроблений рядок значення PATH розділений на окремі шляхи, усі "нульові" шляхи (порожні або рядки, що містять пробіл) можна видалити за допомогою:

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

Оригінальний PATH має як 'null' записи, так і дублікати для тестування:

[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

Нульові шляхи видалено, але все ще є дублікати, наприклад, (1, 3) та (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\

І нарешті, оперізували:

[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

Як функціонує? Мені цікаво, як заповнюється словник "побачене".
mountaincaler
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.