Python에 목록 목록이 있습니다.
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
그리고 중복 요소를 제거하고 싶습니다. 내가 사용할 수있는 목록이 아닌 일반 목록이었다 set
. 그러나 불행히도 그 목록은 해시 할 수 없으며 목록 집합을 만들 수 없습니다. 튜플 만. 따라서 모든 목록을 튜플으로 전환 한 다음 집합을 사용하고 다시 목록으로 돌아갈 수 있습니다. 그러나 이것은 빠르지 않습니다.
가장 효율적인 방법으로이를 수행 할 수있는 방법은 무엇입니까?
위 목록의 결과는 다음과 같아야합니다.
k = [[5, 6, 2], [1, 2], [3], [4]]
나는 질서를 보존하는 것에 관심이 없습니다.
참고 : 이 질문 은 비슷하지만 내가 필요한 것은 아닙니다. 검색했지만 정확한 중복을 찾지 못했습니다.
벤치마킹 :
import itertools, time
class Timer(object):
def __init__(self, name=None):
self.name = name
def __enter__(self):
self.tstart = time.time()
def __exit__(self, type, value, traceback):
if self.name:
print '[%s]' % self.name,
print 'Elapsed: %s' % (time.time() - self.tstart)
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5
N = 100000
print len(k)
with Timer('set'):
for i in xrange(N):
kt = [tuple(i) for i in k]
skt = set(kt)
kk = [list(i) for i in skt]
with Timer('sort'):
for i in xrange(N):
ks = sorted(k)
dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
with Timer('groupby'):
for i in xrange(N):
k = sorted(k)
dedup = list(k for k, _ in itertools.groupby(k))
with Timer('loop in'):
for i in xrange(N):
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
짧은 목록의 경우 가장 빠른 “루프 인”(2 차 방법)입니다. 긴 목록의 경우 groupby 방법을 제외한 모든 사람보다 빠릅니다. 이게 말이 돼?
짧은 목록 (코드에있는 목록)의 경우 100000 회 반복 :
[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665
더 긴 목록의 경우 (코드에있는 항목이 5 번 복제 됨) :
[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
답변
>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> import itertools
>>> k.sort()
>>> list(k for k,_ in itertools.groupby(k))
[[1, 2], [3], [4], [5, 6, 2]]
itertools
이러한 종류의 문제에 대해 가장 빠르고 가장 강력한 솔루션을 제공하는 경우가 많으며 친숙해질 가치 가 있습니다 !-)
편집 : 주석에서 언급했듯이 정상적인 최적화 노력은 많은 입력 (big-O 접근 방식)에 초점을 맞추고 있습니다. 훨씬 쉬워서 노력에 대한 좋은 수익을 제공하기 때문입니다. 그러나 때로는 (본질적으로 성능 한계의 경계를 넘어서는 코드의 깊은 내부 루프에서 “비극적으로 중요한 병목”의 경우) 훨씬 더 자세히 들어가 확률 분포를 제공하고 최적화 할 성능 측정을 결정해야 할 수도 있습니다 (상한 또는 90 번째 백분위 수는 앱에 따라 평균 또는 중앙값보다 더 중요하며, 처음에는 휴리스틱 검사를 수행하여 입력 데이터 특성에 따라 다른 알고리즘을 선택하는 등의 작업을 수행합니다.
“포인트”성능 (특정 입력에 대한 코드 A 대 코드 B)의 세심한 측정은 매우 비용이 많이 드는이 프로세스의 일부이며 표준 라이브러리 모듈 timeit
이 여기에 도움이됩니다. 그러나 쉘 프롬프트에서 사용하는 것이 더 쉽습니다. 예를 들어, 다음은이 문제에 대한 일반적인 접근 방식을 보여주는 짧은 모듈입니다 nodup.py
.
import itertools
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
def doset(k, map=map, list=list, set=set, tuple=tuple):
return map(list, set(map(tuple, k)))
def dosort(k, sorted=sorted, xrange=xrange, len=len):
ks = sorted(k)
return [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
def dogroupby(k, sorted=sorted, groupby=itertools.groupby, list=list):
ks = sorted(k)
return [i for i, _ in itertools.groupby(ks)]
def donewk(k):
newk = []
for i in k:
if i not in newk:
newk.append(i)
return newk
# sanity check that all functions compute the same result and don't alter k
if __name__ == '__main__':
savek = list(k)
for f in doset, dosort, dogroupby, donewk:
resk = f(k)
assert k == savek
print '%10s %s' % (f.__name__, sorted(resk))
온 전성 검사 (방금 수행 할 때 수행됨 python nodup.py
)와 기본 권양 기술 (속도를 위해 각 기능에 대해 일정한 전역 이름을 로컬로 지정)을 확인하여 동일한 기반에 놓으십시오.
이제 작은 예제 목록에서 검사를 실행할 수 있습니다.
$ python -mtimeit -s'import nodup' 'nodup.doset(nodup.k)'
100000 loops, best of 3: 11.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort(nodup.k)'
100000 loops, best of 3: 9.68 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby(nodup.k)'
100000 loops, best of 3: 8.74 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.donewk(nodup.k)'
100000 loops, best of 3: 4.44 usec per loop
2 차 접근법이 중복 된 값이 거의없는 작은 목록에 매력적으로 보이도록 상수가 충분히 작음을 확인합니다. 중복이없는 짧은 목록 :
$ python -mtimeit -s'import nodup' 'nodup.donewk([[i] for i in range(12)])'
10000 loops, best of 3: 25.4 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dogroupby([[i] for i in range(12)])'
10000 loops, best of 3: 23.7 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.doset([[i] for i in range(12)])'
10000 loops, best of 3: 31.3 usec per loop
$ python -mtimeit -s'import nodup' 'nodup.dosort([[i] for i in range(12)])'
10000 loops, best of 3: 25 usec per loop
2 차 접근 방식은 나쁘지 않지만 sort 및 groupby 접근 방식이 더 좋습니다. 기타 등등
(성능에 대한 집착에서 알 수 있듯이)이 작업이 경계를 넘나 드는 애플리케이션의 핵심 내부 루프에있는 경우 다른 대표적인 입력 샘플에 대해 동일한 테스트 세트를 시도해 볼 가치가 있습니다. 하나 또는 다른 접근 방식을 선택합니다 (물론 측정은 빠릅니다).
또한 다른 표현을 유지하는 것도 고려해 볼 가치가 있습니다. k
왜 처음에 튜플 세트가 아닌 목록 목록이어야합니까? 중복 제거 작업이 빈번하고 프로파일 링을 통해 프로그램의 성능 병목 현상이 나타나는 경우, 항상 튜플 집합을 유지하고 필요한 경우에만 목록을 가져 오는 것이 전체적으로 더 빠를 수 있습니다.
답변
수동으로 수행하고 새 k
목록을 만들고 지금까지 찾을 수없는 항목을 추가합니다.
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
new_k = []
for elem in k:
if elem not in new_k:
new_k.append(elem)
k = new_k
print k
# prints [[1, 2], [4], [5, 6, 2], [3]]
이해하기 쉽고 각 요소의 첫 번째 발생 순서를 유지하면 유용하지만 new_k
각 요소 전체를 검색 할 때 복잡성이 2 차적이라고 생각합니다 .
답변
>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> k = sorted(k)
>>> k
[[1, 2], [1, 2], [3], [4], [4], [5, 6, 2]]
>>> dedup = [k[i] for i in range(len(k)) if i == 0 or k[i] != k[i-1]]
>>> dedup
[[1, 2], [3], [4], [5, 6, 2]]
필연적으로 더 빠른지 모르겠지만 튜플과 세트를 사용할 필요는 없습니다.
답변
set
지금까지이 문제에 대한 모든 관련 솔루션은 set
반복하기 전에 전체 를 만들어야합니다 .
목록 목록을 반복하고 “seen”에 추가하여이를 게 으르면서 동시에 순서를 유지할 수 set
있습니다. 그런 다음이 추적기에서 찾을 수없는 경우에만 목록을 생성합니다 set
.
이 unique_everseen
레시피는 itertools
문서 에서 사용할 수 있습니다 . 타사 toolz
라이브러리 에서도 사용할 수 있습니다.
from toolz import unique
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
# lazy iterator
res = map(list, unique(map(tuple, k)))
print(list(res))
[[1, 2], [4], [5, 6, 2], [3]]
참고 tuple
목록이 해쉬하지 않기 때문에 변환이 필요하다.
답변
“긴”목록조차도 꽤 짧습니다. 또한 실제 데이터와 일치하도록 선택 했습니까? 성능은 이러한 데이터가 실제로 어떻게 보이는지에 따라 달라집니다. 예를 들어, 긴 목록을 만들기 위해 짧은 목록이 반복해서 반복됩니다. 즉, 2 차 솔루션이 벤치 마크에서는 선형이지만 실제로는 그렇지 않습니다.
실제로 큰 목록의 경우 설정 코드가 최선의 선택입니다. 공간이 부족하더라도 선형입니다. sort 및 groupby 메서드는 O (n log n)이고 메서드의 루프는 분명히 2 차이므로 n이 실제로 커질 때 어떻게 확장되는지 알 수 있습니다. 이것이 당신이 분석하고있는 데이터의 실제 크기라면 누가 신경을 쓰나요? 작습니다.
덧붙여서, 세트를 만들기 위해 중간 목록을 작성하지 않으면 눈에 띄게 속도가 빨라집니다.
kt = [tuple(i) for i in k]
skt = set(kt)
와
skt = set(tuple(i) for i in k)
실제 솔루션은 더 많은 정보에 따라 달라질 수 있습니다. 목록 목록이 실제로 필요한 표현이라고 확신하십니까?
답변
튜플 및 {} 목록을 사용하여 중복을 제거 할 수 있습니다.
>>> [list(tupl) for tupl in {tuple(item) for item in k }]
[[1, 2], [5, 6, 2], [3], [4]]
>>>
답변
튜플을 키로 사용하여 사전을 만들고 키를 인쇄합니다.
- 튜플을 키로, 인덱스를 값으로 사용하여 사전 생성
- 사전의 키 목록 인쇄
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
dict_tuple = {tuple(item): index for index, item in enumerate(k)}
print [list(itm) for itm in dict_tuple.keys()]
# prints [[1, 2], [5, 6, 2], [3], [4]]