Düz bir listede kopya olup olmadığını nasıl kontrol ederim?


185

Örneğin, liste göz önüne alındığında, ['one', 'two', 'one']algoritma geri dönmeli True, oysa ['one', 'two', 'three']geri dönmelidir False.

Yanıtlar:



54

Yalnızca kısa listeler için önerilir :

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

Do not uzun bir liste üzerinde kullanmak - bu zamanı orantılı alabilir meydanda listesindeki öğelerin sayısının!

Yıkanabilir öğelere sahip daha uzun listeler için (dizeler, sayılar ve c):

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

Öğeleriniz yıkanamazsa (alt listeler, diktler, vb.) Saçlar daha da kızarır, ancak en azından karşılaştırılabilir olmaları durumunda O (N logN) almak yine de mümkün olabilir. Ancak, yapabileceğiniz en iyi performansı elde etmek için öğelerin özelliklerini (yıkanabilir veya değil, karşılaştırılabilir veya değil) bilmeniz veya test etmeniz gerekir - Yıkanabilirler için O (N), yıkanamaz karşılaştırılabilirler için O (N log N), aksi takdirde O (N kare) aşağı ve bu konuda yapabileceği bir şey yok :-(.


21
Denis Otkidach, listeden yeni bir set oluşturup uzunluğunu kontrol ettiğiniz bir çözüm sundu. Avantajı, Python'un içindeki C kodunun ağır kaldırmayı yapmasına izin vermesidir. Çözümünüz Python kodunda dolaşıyor, ancak tek bir eşleşme bulunduğunda kısa devre yapma avantajına sahip. Eğer olasılıklar listenin muhtemelen hiçbir kopyası yoksa, Denis Otkidach'ın versiyonunu seviyorum, ancak olasılıklar listede erken bir kopya olabileceğini düşünüyorsa, bu çözüm daha iyidir.
steveha

1
Denis için daha temiz bir çözüm olduğunu düşünsem bile ayrıntıya değdi.
Steve314

@steveha - erken optimizasyon?
Steve314

@ Steve314, hangi erken optimizasyon? Denis Otkidach'ın yazdığı şekilde yazardım, bu yüzden neden Python Yemek Kitabı şöhretinden Alex Martelli'nin farklı yazdığını anlamaya çalışıyordum. Biraz düşündükten sonra Alex'in kısa devrelerinin versiyonunu fark ettim ve farklılıklar hakkında birkaç düşünce yayınladım. Farklılıkların tartışmasından, kötülüğün kökü olan erken optimizasyona nasıl geçersiniz?
steveha

3
Maddeler yıkanabilirse, ayarlanmış bir çözüm daha doğrudan ve ifade ettiğim şekilde daha hızlı (cevap bilindiği anda çıkar - "kısa devreler", steveha koydu). Önerdiğiniz diktüyü oluşturmak (koleksiyon olarak en hızlı. Sayıcı) elbette çok daha yavaştır ( allher şeyden önce sayımların 1 olması gerekir). Tüm değerlere sahip bir diksiyon Ayrıca, sizin de bahsettiğiniz, bir setkatma değeri olmayan, gülünç, yararsız bir şekilde şişirilmiş bir taklitçidir . Big-O programlamada her şey değildir.
Alex Martelli

12

Bu eski, ama buradaki cevaplar beni biraz farklı bir çözüme götürdü. Anlayışları kötüye kullanmak istiyorsanız, bu şekilde kısa devre yapabilirsiniz.

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

İşlevsel programlama stilinden hoşlanıyorsanız, doctest kullanarak kullanışlı bir işlev, kendi kendine belgelenmiş ve test edilmiş kod .

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

Oradan, dönen çiftin ikinci elemanının boş olup olmadığını kontrol ederek tekliği test edebilirsiniz:

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]

Ayrıştırmayı açıkça oluşturduğunuz için bunun etkili olmadığını unutmayın. Ancak azaltma kullanma hattı boyunca, 5'i cevaplamak için eşdeğer (ancak biraz daha az verimli) bir şeye gelebilirsiniz:

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

Önce ilgili soruları okumalıydım. Bu, stackoverflow.com/questions/1723072/…
Xavier Decoret

1
Bana decompose () lambda işlevi "geçersiz sözdizimi" hatası atar
raffaem

Çünkü lambda argüman listelerindeki paketin açılması Python 3.x'te kaldırılmıştır.
MSeifert

5

Burada sunulan farklı çözümlerin zamanlamalarını karşılaştırmanın yararlı olacağını düşündüm. Bunun için kendi kütüphanemi kullandım simple_benchmark:

resim açıklamasını buraya girin

Gerçekten de bu durumda Denis Otkidach'ın çözümü en hızlı.

Bazı yaklaşımlar da çok daha dik bir eğri sergilemektedir, bunlar elementlerin sayısı ile ikinci dereceden ölçeklenen yaklaşımlardır (Alex Martellis ilk çözümü, wjandrea ve Xavier Decorets çözümlerinin her ikisi). Ayrıca, Keiku'nun panda çözümünün çok büyük bir sabit faktöre sahip olduğu da belirtilmelidir. Ancak daha büyük listeler için neredeyse diğer çözümlere yetişir.

Ve yinelenen ilk konumda olması durumunda. Hangi çözümlerin kısa devre yaptığını görmek için yararlıdır:

resim açıklamasını buraya girin

Burada birkaç yaklaşım kısa devre yapmaz: Kaiku, Frank, Xavier_Decoret (ilk çözüm), Turn, Alex Martelli (ilk çözüm) ve Denis Otkidach tarafından sunulan yaklaşım (kopyasız vakada en hızlı olan).

Buraya kendi kütüphanemden bir fonksiyon ekledim: iteration_utilities.all_distinctbu, çoğaltılmamış durumda en hızlı çözümle rekabet edebilen ve başlangıçta çoğaltma vakası için sabit zamanda (en hızlı olmasa da) çalışan bir işlev içeriyordu .

Kıyaslama kodu:

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)

Ve argümanlar için:


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

Referans için: all_distinct işlevi C ile yazılmıştır .
kullanıcı

5

Kısa süre önce bir jeneratörü kullanarak listedeki tüm kopyaları oluşturmak için ilgili bir soruyu cevapladım . Sadece 'yinelenen varsa' oluşturmak için kullanılırsa, o zaman sadece ilk öğeyi almanız gerekir ve geri kalanı göz ardı edilebilir, bu da nihai kısayoldur.

Bu, doğrudan moooeeeep'ten uyarladığım ilginç bir set tabanlı yaklaşımdır :

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

Buna göre, çiftlerin tam listesi olacaktır list(getDupes(etc)). Bir "dupe" olup olmadığını basitçe test etmek için şu şekilde sarılmalıdır:

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

Bu iyi ölçeklendirilir ve dupe'nin listede olduğu her yerde tutarlı çalışma süreleri sağlar - 1 metreye kadar giriş listeleriyle test ettim. Veriler hakkında bir şey biliyorsanız, özellikle, çiftlerin ilk yarıda ortaya çıkması muhtemeldir veya gerçek dupes'i almak gibi gereksinimlerinizi eğmenize izin veren başka şeyler varsa, o zaman birkaç alternatif dupe bulucu vardır. daha iyi performans gösterebilir. Tavsiye ettiğim iki ...

Basit dict tabanlı yaklaşım, çok okunabilir:

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

Sıralanan listede itertools'tan (esas olarak bir ifilter / izip / tee) yararlanın, sadece ilkleri almak kadar hızlı olmasa da tüm dupes'leri alıyorsanız çok etkilidir:

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

Bunlar tam dupe listesi için denediğim yaklaşımların en iyi performans göstericileriydi , ilk dupe başlangıçtan ortaya doğru 1m eleman listesinde herhangi bir yerde meydana geldi. Sıralama adımının ne kadar az ek yük oluşturduğu şaşırtıcıydı. Kilometreniz değişebilir, ancak işte belirli zamanlanmış sonuçlarım:

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()İkinci kod bloğu çağrı Python 3.x çalışmıyor Bence next(getDupes(l))Python sürümlerinde çalışmalı, bu yüzden bunu değiştirmek mantıklı olabilir.
MSeifert

Ayrıca ifilterve ìzipsadece yerleşik filterve zipPython 3.x ile değiştirilebilir.
MSeifert

@MSeifert çözümü python 2.x için yazıldığı gibi çalışır ve evet, py3 için doğrudan filtre ve harita kullanabilirsiniz ... ancak py2 kod tabanında py3 çözümünü kullanan biri fayda sağlamaz çünkü jeneratör. Açık bu durumda örtük olmaktan iyidir;)
F1Rumors

3

Bunu kısa ve öz bir şekilde yapmanın başka bir yolu Counter'tır .

Sadece orijinal listede herhangi bir kopya olup olmadığını belirlemek için:

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

Veya kopyaları olan öğelerin bir listesini almak için:

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

Bunu en iyi performansı yapmak için buldum çünkü ilk çoğaltıldığında çalışmayı kısa devre yaptı, sonra bu algoritma zaman ve uzay karmaşıklığına sahip O (n) burada n listenin uzunluğu:

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

Setin perde arkasında ne yaptığını gerçekten bilmiyorum, bu yüzden basit tutmak istiyorum.

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

Daha basit bir çözüm aşağıdaki gibidir. Sadece panda .duplicated()yöntemi ile Doğru / Yanlış kontrol edin ve sonra toplamı alın. Lütfen ayrıca pandas.Series.duplicated - pandas 0.24.1 belgelerine bakın

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

Liste paylaşılamayan öğeler içeriyorsa, Alex Martelli'nin çözümünü ancak küme yerine bir liste ile kullanabilirsiniz , ancak daha büyük girdiler için daha yavaştır: O (N ^ 2).

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

0

Basitliği için pyrospade yaklaşımını kullandım ve büyük / küçük harfe duyarlı olmayan Windows kayıt defterinden kısa bir listede değiştirdim.

Ham PATH değer dizesi ayrı yollara bölünmüşse, tüm 'null' yollar (yalnızca boş veya yalnızca boşluk dizeleri) aşağıdakiler kullanılarak kaldırılabilir:

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

Orijinal PATH, test amacıyla hem 'boş' girdilere hem de kopyalara sahiptir:

[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

Boş yollar kaldırıldı, ancak yine de yinelenenleri var, örn., (1, 3) ve (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\

Ve son olarak, çiftler kaldırıldı:

[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

İşlev nasıl çalışır? "Görülen" sözlüğünün nasıl doldurulduğunu merak ediyorum.
mountainscaler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.