파이썬에서 목록이 항목을 공유하는지 테스트


131

나는 있는지 확인하려면 어느 하나 개의 목록에있는 항목의 다른 목록에 존재한다. 아래 코드를 사용하여 간단하게 수행 할 수 있지만이 작업을 수행하는 라이브러리 기능이 있다고 생각합니다. 그렇지 않은 경우 동일한 결과를 얻는 더 파이썬적인 방법이 있습니까?

In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

내가 생각할 수있는 유일한 최적화는 False를 산출 len(...) > 0하기 때문에 삭제 bool(set([]))입니다. 물론 목록을 세트로 유지하면 세트 생성 오버 헤드가 절약됩니다.
msw


1
참고 구별 할 수없는 그 True에서 1False에서 0. not set([1]).isdisjoint([True])도착 True다른 솔루션과 동일합니다.
Dimali

답변:


313

짧은 대답 : use not set(a).isdisjoint(b), 일반적으로 가장 빠릅니다.

두 개의 목록 ab공유하고 항목을 공유 하는지 테스트하는 일반적인 네 가지 방법이 있습니다. 첫 번째 옵션은 다음과 같이 둘 다 세트로 변환하고 교차점을 확인하는 것입니다.

bool(set(a) & set(b))

세트는 파이썬에서 해시 테이블을 사용하여 저장O(1) 되기 때문에 세트를 검색합니다 ( 파이썬 연산자의 복잡성에 대한 자세한 내용 은 여기 참조 ). 이론적으로,이은 O(n+m)에 대한 평균 nm목록에 객체 ab. 그러나 1) 먼저 목록에서 집합을 만들어야하며 무시할 수없는 시간이 걸릴 수 있으며 2) 해시 충돌이 데이터 사이에 드문 것으로 가정합니다.

두 번째 방법은 다음과 같이 목록에서 반복을 수행하는 생성기 표현식을 사용하는 것입니다.

any(i in a for i in b)

이를 통해 내부 검색이 가능하므로 중간 변수에 새 메모리가 할당되지 않습니다. 그것은 또한 첫 번째 발견에서 구제됩니다. 그러나 in연산자는 항상 O(n)목록에 있습니다 ( 여기 참조 ).

제안 된 또 다른 옵션은 목록 중 하나를 반복하고 세트의 다른 하나를 변환하고이 세트의 멤버십을 테스트하는 하이브리드입니다.

a = set(a); any(i in a for i in b)

네 번째 방법은 isdisjoint()(냉동 된) 세트 의 방법을 활용하는 것입니다 ( 여기 참조 ).

not set(a).isdisjoint(b)

검색하는 요소가 배열의 시작 부분 근처에있는 경우 (예 : 정렬 된 경우), 집합 교차 방법이 중개 변수에 새 메모리를 할당해야하므로 생성기 표현식이 선호됩니다.

from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974

리스트 크기에 따른이 예제의 실행 시간 그래프는 다음과 같습니다.

처음에 공유 할 때 요소 공유 테스트 실행 시간

두 축은 ​​모두 로그입니다. 이것은 생성기 표현식에 가장 적합한 경우를 나타냅니다. 보다시피,이 isdisjoint()방법은 매우 작은 목록 크기에 대해 더 나은 반면 생성기 표현식은 큰 목록 크기에 더 좋습니다.

반면에 검색이 하이브리드 및 생성기 표현식의 시작으로 시작함에 따라 공유 요소가 배열의 끝에 체계적으로 있거나 두 목록이 값을 공유하지 않는 경우 분리 및 설정 교차 접근 방식은 생성기 표현과 하이브리드 방식보다 훨씬 빠릅니다.

>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668

마지막에 공유 할 때 요소 공유 테스트 실행 시간

리스트 크기가 클수록 생성기 표현식이 더 느리다는 점에 주목하는 것이 흥미 롭습니다. 이것은 이전 그림의 100000이 아니라 1000 회 반복입니다. 이 설정은 공유 된 요소가 없을 때에도 근사치이며, 분리 및 교차로 접근에 가장 적합한 경우입니다.

다음은 임의의 숫자를 사용한 두 가지 분석입니다 (하나의 기술을 선호하도록 설정을 조작하는 대신).

공유 가능성이 높은 임의로 생성 된 데이터의 요소 공유 테스트 실행 시간 공유 가능성이 높은 임의로 생성 된 데이터의 요소 공유 테스트 실행 시간

높은 공유 가능성 : 요소는에서 임의로 가져옵니다 [1, 2*len(a)]. 낮은 공유 가능성 : 요소는에서 임의로 가져옵니다 [1, 1000*len(a)].

지금까지이 분석에서는 두 목록의 크기가 모두 같다고 가정했습니다. 크기가 다른 두 개의리스트의 경우, 예를 들어이 a훨씬 작다 isdisjoint()항상 빠른 :

처음에 공유 할 때 서로 다른 크기의 두 목록에서 요소 공유 테스트 실행 시간 마지막에 공유 할 때 서로 다른 크기의 두 목록에서 요소 공유 테스트 실행 시간

있는지 확인하십시오 a, 그렇지 않으면 성능이 감소 목록은 작다. 이 실험에서 a목록 크기는로 일정하게 설정되었습니다 5.

요약해서 말하자면:

  • 목록이 매우 작은 경우 (<10 개 요소) not set(a).isdisjoint(b)항상 가장 빠릅니다.
  • 목록의 요소가 정렬되거나 활용할 수있는 규칙적인 구조를 갖는 경우 생성기 표현식 any(i in a for i in b)은 큰 목록 크기에서 가장 빠릅니다.
  • 와 교집합 테스트 not set(a).isdisjoint(b)보다 빠른 항상, bool(set(a) & set(b)).
  • 하이브리드 "목록을 통한 반복 테스트, 설정 테스트" a = set(a); any(i in a for i in b)는 일반적으로 다른 방법보다 느립니다.
  • 생성기 표현과 하이브리드는 요소를 공유하지 않고 목록에 올 때 다른 두 가지 접근법보다 훨씬 느립니다.

대부분의 경우 isdisjoint()공유기 요소가 없을 때 생성기 표현식이 실행하는 데 시간이 훨씬 오래 걸리므로 메소드를 사용하는 것이 가장 좋습니다.


8
그것은 유용한 데이터입니다. big-O 분석이 전부는 아니며 실행 시간에 대한 모든 추론을 끝내줍니다.
Steve Allison

최악의 시나리오는 어떻습니까? any첫 번째 거짓이 아닌 값에서 종료합니다. 일치하는 유일한 값이 끝에있는 목록을 사용하면 다음과 같은 결과를 얻습니다. timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 13.739536046981812 timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,-0,-1)]", number=1000) 0.08102107048034668 ... 반복 횟수는 1000 회입니다.
RobM

2
정보를 주셔서 감사합니다 @RobM. 이것을 반영 하고이 스레드에서 제안 된 다른 기술을 고려하기 위해 답변을 업데이트했습니다.
Soravux

not set(a).isdisjoint(b)두 목록이 멤버를 공유하는지 테스트 해야합니다 . set(a).isdisjoint(b)반환 True두 목록이 경우 없는 멤버를 공유 할 수 있습니다. 답을 수정해야합니까?
Guillochon

1
@Guillochon 머리를 고정 주셔서 감사합니다.
Soravux

25
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

참고 : 위의 내용은 부울을 답으로 원한다고 가정합니다. if명령문 에 사용할 표현식 만 필요한 경우if set(a) & set(b):


5
최악의 경우 O (n + m)입니다. 그러나 단점은 새로운 세트를 만들고 공통 요소가 일찍 발견 될 때 구제되지 않는다는 것입니다.
Matthew Flaschen

1
왜 이런지 궁금 O(n + m)합니다. 내 생각에는 세트가 해시 테이블을 사용하여 구현되므로 in운영자는 O(1)시간이 지남 에 따라 작업 할 수 있습니다 (퇴화 사례 제외). 이 올바른지? 그렇다면 해시 테이블의 대 / 소문자 조회 성능이 최악 인 경우 O(n)와 달리, 대 / 소문자와 달리 O(n * m)성능 이 저하됩니까?
fmark

1
@ fmark : 이론적으로, 당신 말이 맞습니다. 실제로 아무도 신경 쓰지 않습니다. CPython 소스에서 Objects / dictobject.c의 주석을 읽고 (세트는 키만 있고 값이없는 dicts) O (n) 조회 성능을 유발하는 키 목록을 생성 할 수 있는지 확인하십시오.
John Machin

알았어, 분명히 해줘서 고마워, 나는 어떤 마술이 진행되고 있는지 궁금해하고 있었다 :). 실제로 관리 할 필요가 없다는 데 동의하지만 O(n)조회 성능 을 유발할 키 목록을 생성하는 것은 사소한 일입니다 .), pastebin.com/Kn3kAW7u Lafs 만 참조하십시오 .
fmark

2
그래, 알아 또한 나는 당신이 지적한 소스를 읽었습니다. 이것은 비 랜덤 해시 함수 (예 : 내장 함수)의 경우 훨씬 더 마술을 문서화합니다. Java와 같이 임의성이 필요하다고 가정 하여이 stackoverflow.com/questions/2634690/… 과 같은 괴물이되었습니다 . 파이썬은 자바가 아니라는 것을 계속 상기시켜야합니다 (감사합니다!).
fmark

10
def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

이것은 점진적으로 최적이며 (최악의 경우 O (n + m)) any단락 때문에 단락 접근보다 더 나을 수 있습니다 .

예 :

lists_overlap([3,4,5], [1,2,3])

도착하자마자 True를 반환합니다 3 in sb

편집 : 또 다른 변형 (데이브 커비 덕분) :

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

이것은 imap생성자 이해가 아닌 C로 구현 된 반복자 에 의존합니다 . sb.__contains__매핑 기능으로 도 사용 됩니다. 이것이 성능 차이가 얼마나 큰지 모르겠습니다. 여전히 단락됩니다.


1
교차점 접근의 루프는 모두 C 코드로되어 있습니다. 파이썬 코드를 포함하는 접근 방식에는 하나의 루프가 있습니다. 큰 교차점은 비어있는 교차로의 가능성 여부입니다.
John Machin

2
any(itertools.imap(sb.__contains__, a))람다 함수를 사용하지 않기 때문에 여전히 더 빠른 것을 사용할 수도 있습니다 .
Dave Kirby

감사합니다, @Dave. :) 람다를 제거하는 것이 승리라는 데 동의합니다.
Matthew Flaschen

4

any목록 이해와 함께 사용할 수도 있습니다 .

any([item in a for item in b])

6
가능하지만 시간은 O (n * m)이지만 설정된 교차점 접근 시간은 O (n + m)입니다. 목록 이해없이 (을 잃지 않고 []) 할 수 있으며 더 빠르게 실행되고 더 적은 메모리를 사용하지만 시간은 여전히 ​​O (n * m)입니다.
John Machin

1
큰 O 분석이 사실이지만, 작은 값의 n 및 m에 대해 기본 해시 테이블을 작성하는 데 걸리는 시간이 작용할 것으로 생각됩니다. Big O는 해시를 계산하는 데 걸리는 시간을 무시합니다.
Anthony Conyers

2
"해시 테이블"을 구축하는 것은 O (n)로 상각됩니다.
John Machin

1
나는 그것을 얻지 만 당신이 버리는 상수는 꽤 큽니다. 큰 n 값은 중요하지 않지만 작은 값은 중요합니다.
Anthony Conyers

3

파이썬 2.6 이상에서는 다음을 수행 할 수 있습니다.

return not frozenset(a).isdisjoint(frozenset(b))

1
첫 번째 인수로 세트 또는 고정 세트를 제공 할 필요가없는 것 같습니다. 나는 문자열로 시도했지만 효과가 있었다 (즉 : 반복 가능).
Aktau

2

내장 함수 / wa 생성기 표현식을 사용할 수 있습니다.

def list_overlap(a,b): 
     return any(i for i in a if i in b)

John과 Lie가 지적했듯이 두 목록이 공유하는 모든 경우 bool (i) == False 일 때 잘못된 결과를 제공합니다. 그것은해야한다:

return any(i in b for i in a)

1
거짓말 라이언의 말을 증폭 : bool(x)거짓 교차점에있는 모든 항목 x에 대해 잘못된 결과를 제공합니다 . Lie Ryan의 예에서 x는 0 입니다. any(True for i in a if i in b)이미 보듯이 픽스 만 작성하는 것이 좋습니다 any(i in b for i in a).
John Machin

1
수정 : 때 잘못된 결과를 줄 것이다 모든 항목 x교차로에서 그 같은 있습니다 bool(x)입니다 False.
John Machin

1

이 질문은 꽤 오래되었지만 사람들이 세트 대 목록을 논쟁하는 동안 아무도 함께 사용하지 않을 것이라는 것을 알았습니다. Soravux의 예에 따르면

목록에 대한 최악의 경우 :

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

그리고 목록에 대한 가장 좋은 경우 :

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

따라서 두 목록을 반복하는 것보다 훨씬 빠릅니다. 목록에 있는지 여부를 확인하기 위해 목록을 반복하는 것입니다. 숫자가 세트에 있는지 확인하는 데 일정한 시간이 걸리고 목록을 반복하여 확인하면 시간의 길이에 비례하여 시간이 걸리기 때문에 의미가 있습니다. 목록.

따라서 내 결론은 목록반복하고 세트에 있는지 확인하는 것입니다 .


1
isdisjoint()@Toughy로 표시된대로 (냉동) 세트 에서 방법을 사용하는 것이 훨씬 좋습니다. timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)=> 0.00913715362548828
Aktau

1

겹치는 요소가 무엇인지 신경 쓰지 않으면 len결합 된 목록과 목록으로 결합 된 목록을 간단히 확인할 수 있습니다 . 겹치는 요소가 있으면 세트가 짧아집니다.

len(set(a+b+c))==len(a+b+c) 겹치지 않으면 True를 반환합니다.


첫 번째 값이 겹치더라도 크기에 관계없이 전체 목록을 세트로 변환합니다.
피터 우드

1

함수형 프로그래밍 스타일로 다른 것을 던질 것입니다.

any(map(lambda x: x in a, b))

설명:

map(lambda x: x in a, b)

의 요소가 b있는 부울 목록을 반환합니다 a. 그런 다음이 목록은에 전달되며 any, True요소가있는 경우 단순히 반환 됩니다 True.

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