일반 목록에 중복이 있는지 어떻게 확인합니까?


185

예를 들어 list가 주어지면 ['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을 사용하는
것이이

1
부동 소수점 배열에는 종종 작동하지 않습니다. stackoverflow.com/questions/60914705
Manas Dogra

54

짧은 목록에만 권장 됩니다.

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

긴 목록 에는 사용 하지 마십시오. 목록에있는 항목 수의 제곱 에 비례하여 시간이 걸릴 수 있습니다 !

해시 가능한 항목 (문자열, 숫자 및 & C)이있는 더 긴 목록의 경우 :

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

아이템이 해시 가능하지 않은 경우 (하위 목록, dicts 등) 더 비싸지 만 적어도 비교 가능한 경우 O (N logN)을 얻을 수 있습니다. 그러나 가능한 최고의 성능을 얻으려면 항목의 특성 (해시 가능 여부, 비교 가능 여부)을 알고 테스트해야합니다. 그것은 O (N squared)로 내려 가고 그것에 대해 할 수있는 일은 없습니다 :-(.


21
Denis Otkidach는 목록에서 새 세트를 만든 다음 길이를 확인하는 솔루션을 제공했습니다. 그 장점은 파이썬 내부의 C 코드가 많은 노력을 기울일 수 있다는 것입니다. 솔루션은 Python 코드로 반복되지만 단일 일치 항목이 발견되면 단락의 이점이 있습니다. 확률이 목록에 중복이 없을 가능성이 높다면 Denis Otkidach의 버전을 좋아하지만 목록에 초기에 중복이있을 가능성이 높으면이 솔루션이 더 좋습니다.
steveha

1
Denis가 더 깔끔한 솔루션을 가지고 있다고 생각하더라도 세부 사항에 대한 가치가 있습니다.
Steve314

@ steveha-조기 최적화?
Steve314

@ Steve314, 어떤 조기 최적화? 나는 Denis Otkidach가 쓴 방식대로 그것을 작성했을 것이므로 (Python Cookbook 명성의) Alex Martelli가 다르게 쓴 이유를 이해하려고했습니다. 나는 그것에 대해 조금 생각한 후에 Alex의 버전이 단락되었다는 것을 깨달았고 차이점에 대해 몇 가지 생각을 게시했습니다. 차이점에 대한 토론에서 모든 악의 근원 인 조기 최적화에 이르는 방법은 무엇입니까?
steveha

3
항목이 해시 가능하면 세트 솔루션이 더 직접적이며 표현 방식이 더 빠릅니다 (답이 알려진대로 종료됩니다 ( "단락", steveha). 당신이 제안한 dict (컬렉션으로 가장 빠름)을 만드는 것은 물론 훨씬 느립니다 ( all모두 1이 필요합니다 ). 당신이 언급 한 모든 가치 True를 가진 딕 트는 우스꽝스럽고 쓸모없는 부풀어 오른 흉내이며 set부가 가치가 없습니다. Big-O는 프로그래밍의 모든 것이 아닙니다.
Alex Martelli

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]

분해를 명시 적으로 구성하기 때문에 이는 효율적이지 않습니다. 그러나 reduce 사용 라인을 따라 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 2016 년

5

여기에 제시된 다양한 솔루션의 타이밍을 비교하는 것이 유용 할 것이라고 생각했습니다. 이를 위해 내 라이브러리를 사용했습니다 simple_benchmark.

여기에 이미지 설명을 입력하십시오

따라서이 경우 Denis Otkidach 의 솔루션 이 가장 빠릅니다.

일부 접근 방식은 훨씬 가파른 곡선을 나타내며, 요소 수에 따라 2 차적으로 확장되는 접근 방식입니다 (Alex Martellis 첫 번째 솔루션, wjandrea 및 Xavier Decorets 솔루션 모두). 또한 Keiku의 팬더 솔루션은 매우 큰 상수를 가지고 있습니다. 그러나 큰 목록의 경우 다른 솔루션과 거의 일치합니다.

그리고 중복이 첫 번째 위치에있는 경우. 이것은 어떤 솔루션이 단락되는지 확인하는 데 유용합니다.

여기에 이미지 설명을 입력하십시오

여기에는 Kaiku, Frank, Xavier_Decoret (첫 번째 솔루션), Turn, Alex Martelli (첫 번째 솔루션) 및 Denis Otkidach가 제시 한 접근법 (중복없는 경우에서 가장 빠름)이 있습니다.

여기에 내 라이브러리의 함수가 포함되어 있습니다.이 기능은 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)). 듀프가있는 경우 단순히 "if"를 테스트하려면 다음과 같이 랩핑해야합니다.

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

이것은 확장 성이 뛰어나고 Dupe가 목록에있는 어느 곳에서나 일관된 작동 시간을 제공합니다. 최대 1m 항목 목록으로 테스트했습니다. 데이터에 대해 구체적으로 말하면, 듀피가 상반기에 나타날 가능성이 있거나 실제 듀피를 얻는 것과 같이 요구 사항을 왜곡시킬 수있는 다른 것들이 있다면 실제로 대체 듀피 로케이터가 있습니다. 그 성능을 능가 할 수 있습니다. 내가 추천하는 두 가지는 ...

읽기 쉬운 간단한 dict 기반 접근 방식 :

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

이들은 전체 dupe 목록에 대해 시도한 접근법에서 최고의 성과를 냈으며 첫 번째 dupe는 처음부터 중간까지 1m 요소 목록에서 발생했습니다. 정렬 단계가 추가되는 오버 헤드가 얼마나 적은지는 놀라운 일이었습니다. 마일리지가 다를 수 있지만 다음은 구체적인 시간 결과입니다.

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))파이썬 버전에서 작동해야 한다고 생각 하므로 변경하는 것이 좋습니다.
MSeifert 2016 년

또한 ifilterìzip간단하게 내장로 대체 될 수 filterzip파이썬 3.x에서의
MSeifert 2016 년

@MSeifert 솔루션은 작성된대로 Python 2.x에서 작동하며, py3의 경우 필터 및 매핑을 직접 사용할 수 있지만 ... py2 코드베이스에서 py3 솔루션을 사용하는 사람은 작동하지 않기 때문에 이점을 얻지 못합니다. 발전기. 이 경우에는 명시 적이 암시 적보다 낫습니다.)
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

보다 간단한 해결책은 다음과 같습니다. 팬더 .duplicated()방법으로 True / False를 확인한 다음 합산하십시오. pandas.Series.duplicated — pandas 0.24.1 documentation 도 참조하십시오

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

목록에 해싱 할 수없는 항목이 포함 된 경우 Alex Martelli의 솔루션을 사용할 수 있지만 세트 대신 목록을 사용할 수 있지만 더 큰 입력의 경우 속도가 느립니다 (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

기능은 어떻게 작동합니까? 사전 "본"이 어떻게 채워지는지 궁금합니다.
마운틴
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.