Bagaimana saya memeriksa apakah ada duplikat dalam daftar datar?


185

Misalnya, diberi daftar ['one', 'two', 'one'], algoritme harus kembali True, sedangkan yang diberikan ['one', 'two', 'three']harus kembali False.

Jawaban:


398

Gunakan set()untuk menghapus duplikat jika semua nilai di- hashable :

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

17
Sebelum membaca ini saya telah mencoba your_list! = Daftar (set (your_list)) yang tidak akan berfungsi karena urutan elemen akan berubah. Menggunakan len adalah cara yang baik untuk menyelesaikan masalah ini
igniteflow

1
sering tidak bekerja untuk berbagai titik apung. Lihat stackoverflow.com/questions/60914705
Manas Dogra

54

Disarankan hanya untuk daftar pendek :

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

Jangan tidak digunakan pada daftar panjang - dapat mengambil waktu sebanding dengan kuadrat dari jumlah item dalam daftar!

Untuk daftar yang lebih panjang dengan item yang dapat hashable (string, angka, & c):

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

Jika item Anda bukan hashable (sublists, dicts, dll) itu menjadi hairier, meskipun masih mungkin untuk mendapatkan O (N logN) jika mereka setidaknya sebanding. Tetapi Anda perlu mengetahui atau menguji karakteristik item (hashable atau tidak, sebanding atau tidak) untuk mendapatkan kinerja terbaik yang Anda bisa - O (N) untuk hashable, O (N log N) untuk perbandingan yang tidak hashable, jika tidak itu ke O (N kuadrat) dan tidak ada yang bisa dilakukan tentang hal itu :-(.


21
Denis Otkidach menawarkan solusi di mana Anda hanya membangun satu set baru dari daftar, lalu periksa panjangnya. Keuntungannya adalah membiarkan kode C di dalam Python melakukan tugas berat. Solusi Anda loop dalam kode Python, tetapi memiliki keuntungan dari hubungan pendek ketika satu kecocokan telah ditemukan. Jika kemungkinan daftar itu mungkin tidak memiliki duplikat, saya suka versi Denis Otkidach, tetapi jika kemungkinannya adalah mungkin ada duplikat di awal daftar, solusi ini lebih baik.
steveha

1
Layak untuk detail, meskipun saya pikir Denis punya solusi yang lebih rapi.
Steve314

@steveha - optimisasi prematur?
Steve314

@ Steve314, optimasi prematur apa? Saya akan menulis dengan cara yang ditulis Denis Otkidach, jadi saya mencoba memahami mengapa Alex Martelli (dari Python Cookbook fame) menulisnya secara berbeda. Setelah saya memikirkannya sedikit, saya menyadari bahwa versi arus pendek Alex, dan saya memposting beberapa pemikiran tentang perbedaan. Bagaimana Anda beralih dari diskusi tentang perbedaan ke optimasi prematur, akar dari semua kejahatan?
steveha

3
Jika item hashable, solusi yang ditetapkan lebih langsung, dan, cara saya mengekspresikannya, lebih cepat (keluar segera setelah jawabannya diketahui - "sirkuit pendek", steveha menaruhnya). Membangun dikte yang Anda usulkan (tercepat sebagai koleksi. allPenghitung ) tentu saja jauh lebih lambat (membutuhkan jumlah semua 1). Diktik dengan semua nilai Benar, yang juga Anda sebutkan, adalah mimikri konyol yang tidak berguna set, tanpa nilai tambah apa pun. Big-O bukanlah segalanya dalam pemrograman.
Alex Martelli

12

Ini sudah tua, tetapi jawaban di sini membawa saya ke solusi yang sedikit berbeda. Jika Anda siap untuk menyalahgunakan pemahaman, Anda bisa mengalami hubungan arus pendek dengan cara ini.

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

Jika Anda menyukai gaya pemrograman fungsional, berikut adalah fungsi yang berguna, didokumentasikan sendiri dan kode yang diuji menggunakan 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()

Dari sana Anda dapat menguji unicity dengan memeriksa apakah elemen kedua dari pasangan yang dikembalikan kosong:

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]

Perhatikan bahwa ini tidak efisien karena Anda secara eksplisit membangun dekomposisi. Namun sejalan dengan penggunaan pengurangan, Anda dapat menemukan sesuatu yang setara (tapi sedikit kurang efisien) untuk menjawab 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

Seharusnya membaca pertanyaan terkait terlebih dahulu. Ini dijelaskan dalam stackoverflow.com/questions/1723072/…
Xavier Decoret

1
Ini melempar saya kesalahan "sintaks tidak valid" pada fungsi lambda dari decompose ()
raffaem

Itu karena membongkar dalam daftar argumen lambda telah dihapus dengan Python 3.x.
MSeifert

5

Saya pikir akan bermanfaat untuk membandingkan penentuan waktu dari berbagai solusi yang disajikan di sini. Untuk ini saya menggunakan perpustakaan saya sendiri simple_benchmark:

masukkan deskripsi gambar di sini

Jadi memang untuk kasus ini solusi dari Denis Otkidach adalah yang tercepat.

Beberapa pendekatan juga menunjukkan kurva yang jauh lebih curam, ini adalah pendekatan yang skala kuadratik dengan jumlah elemen (solusi pertama Alex Martellis, wjandrea dan kedua solusi Xavier Decorets). Juga penting untuk disebutkan adalah bahwa solusi panda dari Keiku memiliki faktor konstan yang sangat besar. Tetapi untuk daftar yang lebih besar hampir mengejar ketinggalan dengan solusi lain.

Dan jika duplikat berada di posisi pertama. Ini berguna untuk melihat solusi mana yang mengalami hubungan arus pendek:

masukkan deskripsi gambar di sini

Di sini beberapa pendekatan tidak mengalami hubungan pendek: Kaiku, Frank, Xavier_Decoret (solusi pertama), Turn, Alex Martelli (solusi pertama) dan pendekatan yang disajikan oleh Denis Otkidach (yang tercepat dalam kasus tanpa duplikat).

Saya menyertakan fungsi dari perpustakaan saya sendiri di sini: iteration_utilities.all_distinct yang dapat bersaing dengan solusi tercepat dalam case tanpa duplikat dan bekerja dalam waktu konstan untuk kasus duplikat di awal (walaupun tidak secepat tercepat).

Kode untuk tolok ukur:

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)

Dan untuk argumennya:


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

Untuk referensi: Fungsi all_distinct yang ditulis dalam C .
pengguna

5

Saya baru-baru ini menjawab pertanyaan terkait untuk membuat semua duplikat dalam daftar, menggunakan generator. Ini memiliki keuntungan bahwa jika digunakan hanya untuk menetapkan 'jika ada duplikat' maka Anda hanya perlu mendapatkan item pertama dan sisanya dapat diabaikan, yang merupakan jalan pintas utama.

Ini adalah pendekatan berbasis set yang menarik yang saya adaptasi langsung dari moooeeeep :

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

Dengan demikian, daftar lengkap dari dupes akan list(getDupes(etc)). Untuk sekadar menguji "jika" ada dupe, itu harus dibungkus sebagai berikut:

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

Ini berskala dengan baik dan memberikan waktu operasi yang konsisten di mana pun dupe berada dalam daftar - Saya menguji dengan daftar hingga 1m entri. Jika Anda mengetahui sesuatu tentang data, khususnya, bahwa dupes cenderung muncul di babak pertama, atau hal-hal lain yang memungkinkan Anda mengubah persyaratan Anda, seperti perlu mendapatkan dupes yang sebenarnya, maka ada beberapa pelacak dupe alternatif yang benar-benar alternatif. yang mungkin mengungguli. Dua yang saya sarankan adalah ...

Pendekatan berbasis dict sederhana, sangat mudah dibaca:

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

Leverage itertools (dasarnya ifilter / izip / tee) pada daftar yang diurutkan, sangat efisien jika Anda mendapatkan semua dupes meskipun tidak secepat mendapatkan yang pertama:

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

Ini adalah pemain terbaik dari pendekatan yang saya coba untuk daftar dupe penuh , dengan dupe pertama terjadi di mana saja dalam daftar elemen 1m dari awal hingga tengah. Mengejutkan betapa sedikit overhead yang ditambahkan langkah semacam itu. Jarak tempuh Anda mungkin beragam, tetapi inilah hasil spesifik waktu saya:

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

The .next()panggilan dalam blok kode kedua Anda tidak bekerja pada Python 3.x. Saya pikir next(getDupes(l))harus bekerja di versi Python, jadi mungkin masuk akal untuk mengubahnya.
MSeifert

Juga ifilterdan ìzipdapat dengan mudah digantikan oleh built-in filterdan zipdengan Python 3.x.
MSeifert

@MSeifert solusinya berfungsi untuk python 2.x seperti yang tertulis, dan ya, untuk py3 Anda dapat menggunakan filter dan memetakan secara langsung ... tetapi seseorang yang menggunakan solusi py3 dalam basis kode py2 tidak akan mendapatkan manfaat karena tidak akan beroperasi sebagai generator. Eksplisit lebih baik daripada implisit dalam kasus ini;)
F1Rumors

3

Cara lain untuk melakukan ini secara ringkas adalah dengan Counter .

Untuk menentukan apakah ada duplikat dalam daftar asli:

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

Atau untuk mendapatkan daftar item yang memiliki duplikat:

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

Saya menemukan ini untuk melakukan kinerja terbaik karena hubungan pendek operasi ketika diduplikasi pertama kali ditemukan, maka algoritma ini memiliki kompleksitas ruang dan waktu O (n) di mana n adalah panjang daftar:

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

Saya tidak benar-benar tahu apa yang diatur di belakang layar, jadi saya hanya ingin membuatnya tetap sederhana.

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

Solusi yang lebih sederhana adalah sebagai berikut. Cukup periksa Benar / Salah dengan .duplicated()metode panda lalu ambil jumlah. Silakan juga lihat dokumentasi pandas.Series.duplicated - panda 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

Jika daftar berisi item yang tidak dapat dihancurkan, Anda dapat menggunakan solusi Alex Martelli tetapi dengan daftar alih-alih set, meskipun lebih lambat untuk input yang lebih besar: O (N ^ 2).

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

0

Saya menggunakan pendekatan pyrospade, karena kesederhanaannya, dan memodifikasi sedikit pada daftar pendek yang dibuat dari registri Windows case-insensitive.

Jika string nilai PATH mentah dibagi menjadi jalur individual, semua jalur 'null' (string kosong atau hanya spasi) dapat dihapus dengan menggunakan:

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 asli memiliki entri 'nol' dan duplikat untuk tujuan pengujian:

[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

Jalur kosong telah dihapus, tetapi masih memiliki duplikat, misalnya, (1, 3) dan (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\

Dan akhirnya, dupes telah dihapus:

[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

Bagaimana cara kerjanya? Saya ingin tahu bagaimana kamus "dilihat" diisi.
mountainscaler
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.