순서를 유지하면서 목록에서 중복 항목을 어떻게 제거합니까?


770

순서를 유지하면서 파이썬의 목록에서 중복을 제거하는 내장 기능이 있습니까? 중복 세트를 제거하기 위해 세트를 사용할 수는 있지만 원래 순서는 파괴됩니다. 또한 다음과 같이 나 자신을 굴릴 수 있다는 것을 알고 있습니다.

def uniq(input):
  output = []
  for x in input:
    if x not in output:
      output.append(x)
  return output

해당 코드 샘플풀어 주셔서 감사합니다 .

그러나 가능한 경우 내장 또는 더 많은 파이썬 관용구를 사용하고 싶습니다.

관련 질문 : 파이썬에서는 순서를 유지하면서 모든 요소가 고유하도록 목록에서 중복을 제거하는 가장 빠른 알고리즘은 무엇 입니까?

답변:


762

여기 몇 가지 대안이 있습니다. http://www.peterbe.com/plog/uniqifiers-benchmark

가장 빠른 것 :

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

그냥 전화 seen.add하는 seen_add대신에 할당 하는 이유는 무엇 seen.add입니까? 파이썬은 동적 언어이며, seen.add각 반복을 해결하는 것은 지역 변수를 해결하는 것보다 비용이 많이 듭니다. seen.add반복 사이에서 변경되었을 수 있으며 런타임은 그것을 배제 할만 큼 똑똑하지 않습니다. 안전하게 플레이하려면 매번 물체를 확인해야합니다.

동일한 데이터 세트에서이 기능을 많이 사용하려는 경우 다음과 같이 정렬 된 세트를 사용하는 것이 좋습니다. http://code.activestate.com/recipes/528878/

작업 당 O (1) 삽입, 삭제 및 구성원 확인.

(작은 추가 참고 사항 : seen.add()항상을 반환 None하므로 or위의 내용은 논리적 테스트의 필수 부분이 아닌 세트 업데이트를 시도하는 방법으로 만 사용됩니다.)


20
@JesseDhillon seen.add은 반복 사이에서 변경 되었을 수 있으며 런타임은 그것을 배제하기에 충분하지 않습니다. 안전하게 플레이하려면 매번 물체를 확인해야합니다. -로 바이트 코드를 보면 각 반복 에서 멤버에 대해 dis.dis(f)실행되는 것을 볼 수 있습니다 . ideone.com/tz1TllLOAD_ATTRadd
Markus Jarderot

5
나는리스트의 목록에서이 작업을 할 때 내가 얻을 : 형식 오류를 : unhashable 유형을 '목록'
옌스 Timmerman

7
귀하의 솔루션은 가장 빠르지 않습니다. 파이썬 3 (2를 테스트하지 않았 음)에서 이것은 더 빠릅니다 (300k 항목 목록-0.045s (귀하의) 대 0.035s (이것)) : seen = set (); x가 보이지 않거나 보이지 않으면 x를 x로 줄 seen.add (x)] 사용자가 본 seen_add 라인의 속도 효과를 찾을 수 없습니다
user136036

3
@ user136036 테스트에 연결하십시오. 몇 번이나 뛰었습니까? seen_add개선되었지만 시간은 시스템 리소스의 영향을받을 수 있습니다. 전체 타이밍보고 관심을 가질만한
jamylak

2
파이썬 코드를 작성하는 사람에게는 루프 당 몇 나노초를 짜기 위해 가독성과 일반적으로 동의하는 파이썬 규칙을 희생하기 전에 두 번 생각해야합니다. 유무에 관계없이 테스트 seen_add = seen.add하면 속도가 1 % 증가합니다. 거의 중요하지 않습니다.
sleblanc

343

2016 년 편집

Raymond가 지적했듯이OrderedDict C로 구현 된 python 3.5 이상에서는 목록 이해 접근 방식이 느립니다 OrderedDict(실제로 목록이 필요하지 않은 한 입력이 매우 짧은 경우에만). 따라서 3.5+에 가장 적합한 솔루션은 OrderedDict입니다.

중요 편집 2015

@abarnert가 지적 했듯이 more_itertools라이브러리 ( pip install more_itertools)에는 목록 이해에서 읽을 수없는 ( ) 돌연변이unique_everseen 없이이 문제를 해결하기 위해 작성된 함수가 포함되어 있습니다 . 이것은 또한 가장 빠른 솔루션입니다.not seen.add

>>> from  more_itertools import unique_everseen
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(unique_everseen(items))
[1, 2, 0, 3]

단 하나의 간단한 라이브러리 가져 오기 및 해킹 없음. 이것은 itertools 레시피의 구현에서 비롯됩니다 unique_everseen.

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in filterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

파이썬 2.7+에서 허용되는 일반적인 관용구 (작동하지만 속도에 최적화되지 않았으므로 이제는 unique_everseen)를 사용합니다 collections.OrderedDict.

런타임 : O (N)

>>> from collections import OrderedDict
>>> items = [1, 2, 0, 1, 3, 2]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]

이것은 다음보다 훨씬 멋지게 보입니다.

seen = set()
[x for x in seq if x not in seen and not seen.add(x)]

못생긴 해킹을 사용하지 않습니다 .

not seen.add(x)

이는 set.add항상 반환되는 인플레 이스 (in-place) 메소드라는 사실에 의존 None하므로 not None평가됩니다 True.

그러나 해킹 솔루션은 동일한 런타임 복잡도 O (N)를 갖지만 원시 속도가 더 빠릅니다.


5
열쇠를 가져 가기 위해 어떤 종류의 맞춤법으로 변환합니까? 또 다른 목발.
Nakilon

3
@Nakilon 나는 그것이 어떻게 버팀목인지 알지 못한다. 변경 가능한 상태를 노출하지 않으므로 그 의미에서 매우 깨끗합니다. 내부적으로 파이썬 세트는 dict () ( stackoverflow.com/questions/3949310/… ) 로 구현 되므로 기본적으로 인터프리터가 수행 한 작업을 수행하고 있습니다.
Imran

부작용과 do [seen.add(x) for x in seq if x not in seen]를 사용하거나 이해 부작용을 좋아하지 않는 경우 for루프를 사용하십시오 . for x in seq: seen.add(x) if x not in seen else None(여전히 하나의 라이너이지만,이 경우에는 하나의 라이너가 어리석은 속성이라고 생각합니다. 솔루션을 제공합니다.
엘리

@EMS 순서를 유지하지 않습니다. 당신도 마찬가지로 할 수 있습니다 seen = set(seq).
flornquake

1
@CommuSoft 동의합니다. 비록 거의 최악의 경우는 거의 항상 O (n)입니다.
jamylak

110

Python 2.7 에서 원래 순서대로 유지하면서 iterable에서 중복을 제거하는 새로운 방법은 다음과 같습니다.

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.5 에서 OrderedDict에는 C 구현이 있습니다. 내 타이밍에 따르면 이것이 현재 Python 3.5에 대한 다양한 접근 방식 중 가장 빠르고 가장 짧습니다.

Python 3.6 에서는 일반 dict이 순서가 작고 간결 해졌습니다. (이 기능은 CPython 및 PyPy 용이지만 다른 구현에는 없을 수 있습니다). 이를 통해 주문을 유지하면서 새로운 가장 빠른 중복 제거 방법을 얻을 수 있습니다.

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

Python 3.7 에서는 일반 구현이 모든 구현에서 순서대로 보장됩니다. 가장 짧고 빠른 솔루션은 다음과 같습니다.

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

@max에 대한 응답 : 3.6 또는 3.7로 이동하고 OrderedDict 대신 일반 dict를 사용하면 다른 방식으로 성능을 실제로 능가 할 수 없습니다. 사전은 밀도가 높으며 오버 헤드가 거의없는 목록으로 쉽게 변환됩니다. 대상 목록은 len (d)로 사전 크기 조정되어 목록 이해에서 발생하는 모든 크기 조정을 저장합니다. 또한 내부 키 목록은 밀도가 높으므로 포인터를 복사하는 것이 목록 복사와 거의 같습니다.


더 빠른 내 컴퓨터 (파이썬 3.5)에 대한 다른 접근 방식보다 같은만큼 내가 변환하지 않습니다 OrderedDict결국 목록에. 목록으로 변환 해야하는 경우 작은 입력의 경우 목록 이해 접근 방식이 여전히 최대 1.5 배 빠릅니다. 즉,이 솔루션은 훨씬 깨끗합니다.
최대

7
(목록의 목록으로) 임의의 요소와 반복 가능 객체에 대한 동등한을 가지고 좋은 것 - 만 잡았다 반복 가능한 "요소"해쉬해야한다는 것입니다
Mr_and_Mrs_D

dict에 대한 삽입 순서 반복은 중복을 제거하는 것보다 더 많은 사용 사례를 제공하는 기능을 제공합니다. 예를 들어, 과학적 분석은 비 결정적 dict 반복이 지원하지 않는 재현 가능한 계산에 의존합니다 . 재현성은 전산 과학 모델링에서 현재 주요 목표이므로이 새로운 기능을 환영합니다. 결정적 인 dict으로 작성하는 것은 사소한 일이지만, 고성능의 결정 론적 set()은 더 순진한 사용자가 재현 가능한 코드를 개발하는 데 도움이됩니다.
Arthur

41
sequence = ['1', '2', '3', '3', '6', '4', '5', '6']
unique = []
[unique.append(item) for item in sequence if item not in unique]

고유 → ['1', '2', '3', '6', '4', '5']


28
이 글은 그이 실행을 주목할 필요가n^2
goncalopp

25
Ick. 2 경고 : 멤버쉽 테스트 목록 사용 (느린 O (N)) 및 부작용에 대한 목록 이해 사용 ( None프로세스에서 다른 참조 목록 작성 !)
Martijn Pieters

1
@MartijnPieters에 동의합니다 . 부작용에 대한 목록 이해의 이유는 없습니다 . 그냥 사용하는 for대신 루프
jamylak

31

죽은 말을 걷지 말아라 (이 질문은 매우 오래되었고 이미 많은 정답이 있습니다). 여기에 많은 상황에서 매우 빠르며 사용하기가 쉽지 않은 팬더를 사용하는 솔루션이 있습니다.

import pandas as pd

my_list = [0, 1, 2, 3, 4, 1, 2, 3, 5]

>>> pd.Series(my_list).drop_duplicates().tolist()
# Output:
# [0, 1, 2, 3, 4, 5]

27
from itertools import groupby
[ key for key,_ in groupby(sortedList)]

목록을 정렬 할 필요조차 없으며 충분한 조건은 동일한 값이 함께 그룹화된다는 것입니다.

편집 : "보존 순서"는 목록이 실제로 정렬되어 있음을 의미한다고 가정했습니다. 그렇지 않은 경우 MizardX의 솔루션이 적합합니다.

커뮤니티 편집 : 그러나 이것은 "중복 연속 요소를 단일 요소로 압축하는"가장 우아한 방법입니다.


1
그러나 이것은 질서를 유지하지 않습니다!

1
흠, 이것은 문제가됩니다. 왜냐하면 목록에서 한 번 반복하지 않고 동일한 값이 그룹화되어 있음을 보장 할 수 없기 때문에 중복을 제거 할 수 있습니다.
Josh Glover

"주문 유지"는 목록이 실제로 주문되었다는 것을 암시한다고 가정했습니다.
Rafał Dowgird

1
입력 목록의 사양이 약간 불분명 할 수도 있습니다. [2, 1, 3, 1]과 같이 그룹화 할 필요도 없습니다. 따라서 유지할 값과 삭제할 값은 무엇입니까?

1
@igorkf 쌍의 두 번째 요소를 무시합니다.
Rafał Dowgird

24

주문을 유지하고 싶다면

당신은 이것을 시도 할 수 있습니다 :

list1 = ['b','c','d','b','c','a','a']    
list2 = list(set(list1))    
list2.sort(key=list1.index)    
print list2

또는 이와 유사하게 다음을 수행 할 수 있습니다.

list1 = ['b','c','d','b','c','a','a']  
list2 = sorted(set(list1),key=list1.index)  
print list2 

당신은 또한 이것을 할 수 있습니다 :

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
for i in list1:    
    if not i in list2:  
        list2.append(i)`    
print list2

다음과 같이 쓸 수도 있습니다 :

list1 = ['b','c','d','b','c','a','a']    
list2 = []    
[list2.append(i) for i in list1 if not i in list2]    
print list2 

3
첫 두 답변은 정렬 기능을 사용하여 목록의 순서를 다시 작성할 수 있다고 가정하지만 그렇지 않을 수도 있습니다.
Richard

5
대부분의 답변은 성능에 중점을 둡니다. 성능에 대해 걱정할만큼 크지 않은 목록의 경우 sorted (set (list1), key = list1.index)가 내가 본 것 중 가장 좋습니다. 추가 가져 오기, 추가 기능, 추가 변수가 없으며 매우 간단하고 읽기 쉽습니다.
Derek Veit

23

에서 파이썬 3.7 이상, 사전이되어 보장 자신의 키 삽입 순서를 기억. 에 대한 대답 질문은 문제의 현재 상태를 요약 한 것입니다.

따라서 OrderedDict솔루션은 더 이상 사용되지 않으며 수입 명세서없이 간단하게 발행 할 수 있습니다.

>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> list(dict.fromkeys(lst))
[1, 2, 3, 4]

12

또 다른 아주 오래된 질문에 대한 또 다른 매우 늦은 답변 :

itertools조리법 사용하여이 작업을 수행하는 기능이 seen설정 기술을하지만, :

  • 표준 key기능을 처리 합니다.
  • 보기 흉한 해킹을 사용하지 않습니다.
  • seen.addN 번 조회하는 대신 사전 바인딩을 통해 루프를 최적화합니다 . ( f7또한이 작업을 수행하지만 일부 버전은 그렇지 않습니다.)
  • 을 사용하여 루프를 최적화 ifilterfalse하므로 Python의 고유 한 요소를 모두 대신 반복해야합니다. (물론 여전히 내부의 모든 항목을 반복 ifilterfalse하지만 C에서는 훨씬 빠릅니다.)

실제로보다 빠릅 f7니까? 데이터에 따라 다르므로 테스트하고 확인해야합니다. 결국 목록을 원하면 f7listcomp를 사용하면 할 수 없습니다. ing append대신 직접 yield생성하거나 생성기에 list함수 를 공급할 수는 있지만 listcomp 내에서 LIST_APPEND만큼 빠를 수는 없습니다. 장식하고 싶을 때 DSU가 필요없는 이해하기 쉽고 재사용이 가능하며 이미 작성된 기능을 갖추는 것이 중요합니다.

모든 레시피와 마찬가지로 more-iterools .

key대소 문자를 원하지 않으면 다음 과 같이 단순화 할 수 있습니다.

def unique(iterable):
    seen = set()
    seen_add = seen.add
    for element in itertools.ifilterfalse(seen.__contains__, iterable):
        seen_add(element)
        yield element

나는 more-itertools이것이 최선의 대답이라는 것을 완전히 간과했다 . 단순 from more_itertools import unique_everseen list(unique_everseen(items))내보다 훨씬 빠른 접근법과 허용되는 답변보다 훨씬 낫습니다. 라이브러리 다운로드가 그만한 가치가 있다고 생각합니다. 나는 커뮤니티 위키에 답을하고 이것을 추가 할 것이다.
jamylak

12

그냥 외부 모듈에서 같은 기능의 다른 (매우 성능이 좋은) 구현을 추가하는 1 : iteration_utilities.unique_everseen:

>>> from iteration_utilities import unique_everseen
>>> lst = [1,1,1,2,3,2,2,2,1,3,4]

>>> list(unique_everseen(lst))
[1, 2, 3, 4]

타이밍

좀 타이밍 (파이썬 3.6)과 빠른 포함 내가 테스트 한 모든 다른 대안보다 있다는이 보여 주 었는가 OrderedDict.fromkeys, f7more_itertools.unique_everseen:

%matplotlib notebook

from iteration_utilities import unique_everseen
from collections import OrderedDict
from more_itertools import unique_everseen as mi_unique_everseen

def f7(seq):
    seen = set()
    seen_add = seen.add
    return [x for x in seq if not (x in seen or seen_add(x))]

def iteration_utilities_unique_everseen(seq):
    return list(unique_everseen(seq))

def more_itertools_unique_everseen(seq):
    return list(mi_unique_everseen(seq))

def odict(seq):
    return list(OrderedDict.fromkeys(seq))

from simple_benchmark import benchmark

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: list(range(2**i)) for i in range(1, 20)},
              'list size (no duplicates)')
b.plot()

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

그리고 차이가 나는지 확인하기 위해 더 많은 복제본으로 테스트를 수행했는지 확인하십시오.

import random

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [random.randint(0, 2**(i-1)) for _ in range(2**i)] for i in range(1, 20)},
              'list size (lots of duplicates)')
b.plot()

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

그리고 하나의 값만 포함하는 것 :

b = benchmark([f7, iteration_utilities_unique_everseen, more_itertools_unique_everseen, odict],
              {2**i: [1]*(2**i) for i in range(1, 20)},
              'list size (only duplicates)')
b.plot()

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

이 모든 경우에 iteration_utilities.unique_everseen기능이 가장 빠릅니다 (내 컴퓨터에서).


iteration_utilities.unique_everseen함수는 입력에서 해싱 할 수없는 값을 처리 할 수도 있습니다 (단 , 값을 해시 할 수있는 경우 O(n*n)성능 대신 성능 사용 O(n)).

>>> lst = [{1}, {1}, {2}, {1}, {3}]

>>> list(unique_everseen(lst))
[{1}, {2}, {3}]

1 면책 조항 : 나는 그 패키지의 저자입니다.


이 라인의 필요성을 이해하지 못합니다 : seen_add = seen.add-벤치 마크에 필요한가요?
Alex

@Alex 이것은 이 답변에 제공된 접근 방식 입니다. 거기에 묻는 것이 더 합리적입니다. 방금 그 대답의 접근 방식을 사용하여 타이밍을 비교했습니다.
MSeifert

dict.fromkeys()방법을 차트에 추가 할 수 있습니까?
보리스

타이밍을 똑같이 할 것인지 확실하지 않습니다. 당신은 그것보다 훨씬 빠르다고 생각 ordereddict.fromkeys합니까?
MSeifert

"이 iteration_utilities.unique_everseen 함수는 입력에서 해싱 할 수없는 값도 처리 할 수 ​​있습니다"-예, 이것은 정말 중요합니다. dicts 등의 dict dict 목록이있는 경우 소규모로도 작업을 수행하는 유일한 방법입니다.
Roko Mijic

6

MizardX를 기반으로 해시 가능 유형 (예 : 목록 목록)이없는 경우 :

def f7_noHash(seq)
    seen = set()
    return [ x for x in seq if str( x ) not in seen and not seen.add( str( x ) )]

3

nub목록에 대한 Haskell의 기능을 정의하는 데 사용되는 재귀 아이디어를 차용하면 다음과 같은 재귀 접근 방식입니다.

def unique(lst):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: x!= lst[0], lst[1:]))

예 :

In [118]: unique([1,5,1,1,4,3,4])
Out[118]: [1, 5, 4, 3]

나는 데이터 크기를 늘리기 위해 그것을 시도하고 하위 선형 시간 복잡성을 보았습니다 (결정적이지는 않지만 정상적인 데이터에는 괜찮을 것입니다).

In [122]: %timeit unique(np.random.randint(5, size=(1)))
10000 loops, best of 3: 25.3 us per loop

In [123]: %timeit unique(np.random.randint(5, size=(10)))
10000 loops, best of 3: 42.9 us per loop

In [124]: %timeit unique(np.random.randint(5, size=(100)))
10000 loops, best of 3: 132 us per loop

In [125]: %timeit unique(np.random.randint(5, size=(1000)))
1000 loops, best of 3: 1.05 ms per loop

In [126]: %timeit unique(np.random.randint(5, size=(10000)))
100 loops, best of 3: 11 ms per loop

또한 이것이 다른 작업에 의해 고유성으로 쉽게 일반화 될 수 있다는 것이 흥미 롭습니다. 이처럼 :

import operator
def unique(lst, cmp_op=operator.ne):
    return [] if lst==[] else [lst[0]] + unique(filter(lambda x: cmp_op(x, lst[0]), lst[1:]), cmp_op)

예를 들어, 다음과 같이 고유성 목적으로 "평등"인 것처럼 같은 정수로 반올림 개념을 사용하는 함수를 전달할 수 있습니다.

def test_round(x,y):
    return round(x) != round(y)

그러면 unique (some_list, test_round)는 고유성이 더 이상 전통적인 평등을 의미하지 않는 (이 문제에 대한 일종의 세트 기반 또는 dict-key 기반 접근법을 사용하여 암시되는) 목록의 고유 요소를 제공합니다. 요소가 반올림 할 수있는 가능한 각 정수 K에 대해 K로 반올림하는 첫 번째 요소 만 예를 들면 다음과 같습니다.

In [6]: unique([1.2, 5, 1.9, 1.1, 4.2, 3, 4.8], test_round)
Out[6]: [1.2, 5, 1.9, 4.2, 3]

1
각각의 연속적인 재귀 호출의 사용은 filter이전 호출의 이점을 거의 얻지 못 하기 때문에 고유 요소의 수가 총 요소 수에 비해 매우 큰 경우 성능이 저하됩니다 . 그러나 고유 한 요소의 수가 배열 크기에 비해 적 으면 성능이 꽤 좋아야합니다.
ely

3

5 배 더 빨라졌지만 변형이 더 정교 해졌습니다.

>>> l = [5, 6, 6, 1, 1, 2, 2, 3, 4]
>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

설명:

default = (list(), set())
# use list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

>>> reduce(reducer, l, default)[0]
[5, 6, 1, 2, 3, 4]

3

'_ [1]'기호로 작성되는 목록 이해를 참조 할 수 있습니다.
예를 들어 다음 함수는 목록 이해를 참조하여 순서를 변경하지 않고 요소 목록을 고유 화합니다.

def unique(my_list): 
    return [x for x in my_list if x not in locals()['_[1]']]

데모:

l1 = [1, 2, 3, 4, 1, 2, 3, 4, 5]
l2 = [x for x in l1 if x not in locals()['_[1]']]
print l2

산출:

[1, 2, 3, 4, 5]

2
또한 O (n ^ 2) 연산으로 설정합니다 (일정한 조회 시간이있는 set / dict 생성 및 이전에 보이지 않은 요소 만 추가하는 경우).
ely

이것은 내가 믿는 Python 2.6입니다. 그리고 그렇습니다 그것은 O (N ^ 2)입니다
jamylak

2

MizardX의 답변은 여러 가지 접근 방식의 훌륭한 모음을 제공합니다.

이것이 큰 소리로 생각하면서 생각 해낸 것입니다.

mylist = [x for i,x in enumerate(mylist) if x not in mylist[i+1:]]

귀하의 솔루션은 훌륭하지만 각 요소의 마지막 모양이 필요합니다. 첫 등장으로 사용하려면 : [x는 mylist에없는 경우 열거 형 (mylist)의 i, x (x) : x)
Rivka

7
목록에서 검색하는 O(n)작업 은 작업이며 각 항목에 대해 수행하므로 솔루션의 결과 복잡성은 다음과 같습니다 O(n^2). 이것은 사소한 문제에는 용납 할 수 없습니다.
Nikita Volkov

2

간단한 방법은 다음과 같습니다.

list1 = ["hello", " ", "w", "o", "r", "l", "d"]
sorted(set(list1 ), key=lambda x:list1.index(x))

출력을 제공합니다.

["hello", " ", "w", "o", "r", "l", "d"]

1

당신은 추악한 목록 이해 해킹을 할 수 있습니다.

[l[i] for i in range(len(l)) if l.index(l[i]) == i]

를 선호 i,e in enumerate(l)하십시오 l[i] for i in range(len(l)).
Evpok

1

상대적으로 효과적인 접근 배열 :_sorted_numpy

b = np.array([1,3,3, 8, 12, 12,12])    
numpy.hstack([b[0], [x[0] for x in zip(b[1:], b[:-1]) if x[0]!=x[1]]])

출력 :

array([ 1,  3,  8, 12])

1
l = [1,2,2,3,3,...]
n = []
n.extend(ele for ele in l if ele not in set(n))

O (1)을 사용하는 생성자 표현식은 새 목록에 요소를 포함할지 여부를 결정하기 위해 집합을 찾습니다.


1
extend확장 된 것 (따라서 +1)에 의존하지만 set(n)각 단계 (선형 적)에서 재 계산 되는 생성기 표현식을 영리하게 사용 하면 전체 이차 접근 방법에 충돌합니다. 실제로 이것은 단순히을 사용하는 것보다 거의 나쁩니다 ele in n. 단일 멤버쉽 테스트를위한 세트를 만드는 것은 세트 생성 비용이 들지 않습니다. 여전히-흥미로운 접근 방식입니다.
John Coleman

1

간단한 재귀 솔루션 :

def uniquefy_list(a):
    return uniquefy_list(a[1:]) if a[0] in a[1:] else [a[0]]+uniquefy_list(a[1:]) if len(a)>1 else [a[0]]

1

순서대로 중복 값을 제거하지만 나머지 항목의 순서는 유지합니다. 범용 생성기 기능 사용.

# for hashable sequence
def remove_duplicates(items):
    seen = set()
    for item in items:
        if item not in seen:
            yield item
            seen.add(item)

a = [1, 5, 2, 1, 9, 1, 5, 10]
list(remove_duplicates(a))
# [1, 5, 2, 9, 10]



# for unhashable sequence
def remove_duplicates(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
            seen.add(val)

a = [ {'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
list(remove_duplicates(a, key=lambda d: (d['x'],d['y'])))
# [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]

1

팬더 사용자는 체크 아웃해야합니다 pandas.unique.

>>> import pandas as pd
>>> lst = [1, 2, 1, 3, 3, 2, 4]
>>> pd.unique(lst)
array([1, 2, 3, 4])

이 함수는 NumPy 배열을 반환합니다. 필요한 경우 tolist메소드 를 사용하여 목록으로 변환 할 수 있습니다 .


1
좋은데 나는 팬더를 사용하는 것을 결코 상상하지 못하지만 그것은 효과가 있습니다
seralouk

0

하나의 라이너가 필요하면 도움이 될 것입니다.

reduce(lambda x, y: x + y if y[0] not in x else x, map(lambda x: [x],lst))

... 작동하지만 내가 틀렸다면 수정해야합니다.


그것은이다 조건식 그래서 그것의 좋은
code22

0

일상적으로을 사용 pandas하고 성능보다 미학을 선호하는 경우 내장 기능을 고려하십시오 pandas.Series.drop_duplicates.

    import pandas as pd
    import numpy as np

    uniquifier = lambda alist: pd.Series(alist).drop_duplicates().tolist()

    # from the chosen answer 
    def f7(seq):
        seen = set()
        seen_add = seen.add
        return [ x for x in seq if not (x in seen or seen_add(x))]

    alist = np.random.randint(low=0, high=1000, size=10000).tolist()

    print uniquifier(alist) == f7(alist)  # True

타이밍:

    In [104]: %timeit f7(alist)
    1000 loops, best of 3: 1.3 ms per loop
    In [110]: %timeit uniquifier(alist)
    100 loops, best of 3: 4.39 ms per loop

0

이것은 순서를 유지하고 O (n) 시간에 실행됩니다. 기본적으로 아이디어는 중복이 발견 된 곳마다 구멍을 만들고 바닥으로 내려 놓는 것입니다. 읽기 및 쓰기 포인터를 사용합니다. 중복이 발견 될 때마다 읽기 포인터 만 진행하고 중복 포인터에 쓰기 포인터가 남아서 덮어 씁니다.

def deduplicate(l):
    count = {}
    (read,write) = (0,0)
    while read < len(l):
        if l[read] in count:
            read += 1
            continue
        count[l[read]] = True
        l[write] = l[read]
        read += 1
        write += 1
    return l[0:write]

0

가져온 모듈 또는 세트를 사용하지 않는 솔루션 :

text = "ask not what your country can do for you ask what you can do for your country"
sentence = text.split(" ")
noduplicates = [(sentence[i]) for i in range (0,len(sentence)) if sentence[i] not in sentence[:i]]
print(noduplicates)

출력을 제공합니다.

['ask', 'not', 'what', 'your', 'country', 'can', 'do', 'for', 'you']

이것은 매번 O (N ** 2) 복잡성 + 목록 슬라이싱입니다.
Jean-François Fabre

0

적절한 방법

이 방법은 목록의 모든 요소에 대해 목록을 선형으로 조회하기 때문에 2 차법입니다 (따라서 목록 정렬에 따른 비용을 추가해야 함). del s ).

즉, 목록의 끝에서 시작하여 왼쪽의 하위 목록에있는 각 용어를 제거하여 원점으로 진행하면 제자리에서 작업 할 수 있습니다

코드 에서이 아이디어는 단순히

for i in range(len(l)-1,0,-1): 
    if l[i] in l[:i]: del l[i] 

간단한 구현 테스트

In [91]: from random import randint, seed                                                                                            
In [92]: seed('20080808') ; l = [randint(1,6) for _ in range(12)] # Beijing Olympics                                                                 
In [93]: for i in range(len(l)-1,0,-1): 
    ...:     print(l) 
    ...:     print(i, l[i], l[:i], end='') 
    ...:     if l[i] in l[:i]: 
    ...:          print( ': remove', l[i]) 
    ...:          del l[i] 
    ...:     else: 
    ...:          print() 
    ...: print(l)
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5, 2]
11 2 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4, 5]
10 5 [6, 5, 1, 4, 6, 1, 6, 2, 2, 4]: remove 5
[6, 5, 1, 4, 6, 1, 6, 2, 2, 4]
9 4 [6, 5, 1, 4, 6, 1, 6, 2, 2]: remove 4
[6, 5, 1, 4, 6, 1, 6, 2, 2]
8 2 [6, 5, 1, 4, 6, 1, 6, 2]: remove 2
[6, 5, 1, 4, 6, 1, 6, 2]
7 2 [6, 5, 1, 4, 6, 1, 6]
[6, 5, 1, 4, 6, 1, 6, 2]
6 6 [6, 5, 1, 4, 6, 1]: remove 6
[6, 5, 1, 4, 6, 1, 2]
5 1 [6, 5, 1, 4, 6]: remove 1
[6, 5, 1, 4, 6, 2]
4 6 [6, 5, 1, 4]: remove 6
[6, 5, 1, 4, 2]
3 4 [6, 5, 1]
[6, 5, 1, 4, 2]
2 1 [6, 5]
[6, 5, 1, 4, 2]
1 5 [6]
[6, 5, 1, 4, 2]

In [94]:                                                                                                                             

게시하기 전에 'place'에 대한 답변 본문을 검색했습니다. 다른 사람들이 비슷한 방식으로 문제를 해결했다면 알려 주시면 최대한 빨리 답변을 삭제하겠습니다.
gboffi

l[:] = <one of the the faster methods>내부 작업을 원한다면 사용할 수 있습니다.
timgeb

@timgeb 예 그리고 아니오… 내가 할 a=[1]; b=a; a[:]=[2]b==[2]가치는 True있고 우리는 그것을 제자리에서하고 있다고 말할 수 있지만, 제안한 것은 새로운 공간을 사용하여 새로운 목록을 만들고, 오래된 데이터를 새로운 데이터로 바꾸고 가비지 수집에 대한 오래된 데이터는 더 이상 참조되지 않기 때문에 제자리에서 작동한다고 말하는 것은 개념에서 약간의 개념을 확장하는 것입니다. 네,하지만 미리 말씀 드렸습니다.
gboffi

0

zmk의 접근 방식은 목록 이해력을 사용하여 매우 빠르지 만 순서를 자연스럽게 유지합니다. 대소 문자 구분 문자열에 적용하기 위해 쉽게 수정할 수 있습니다. 이것은 또한 원래의 사례를 보존합니다.

def DelDupes(aseq) :
    seen = set()
    return [x for x in aseq if (x.lower() not in seen) and (not seen.add(x.lower()))]

밀접하게 관련된 기능은 다음과 같습니다.

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

0

하나의 라이너 목록 이해 :

values_non_duplicated = [value for index, value in enumerate(values) if value not in values[ : index]]

이 이전 위치에 있지 않은지 확인하기 위해 조건부를 추가하기 만하면 됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.