값이 목록에 있는지 확인하는 가장 빠른 방법


816

값이 목록 (수백만 개의 값이 포함 된 목록)에 존재하고 그 색인이 무엇인지 아는 가장 빠른 방법은 무엇입니까?

이 예제와 같이 목록의 모든 값이 고유하다는 것을 알고 있습니다.

내가 시도하는 첫 번째 방법은 (실제 코드에서 3.8 초)입니다.

a = [4,2,3,1,5,6]

if a.count(7) == 1:
    b=a.index(7)
    "Do something with variable b"

두 번째 방법은 (2 배 빠름 : 실제 코드의 경우 1.9 초)입니다.

a = [4,2,3,1,5,6]

try:
    b=a.index(7)
except ValueError:
    "Do nothing"
else:
    "Do something with variable b"

Stack Overflow 사용자가 제안한 방법 (실제 코드의 경우 2.74 초) :

a = [4,2,3,1,5,6]
if 7 in a:
    a.index(7)

내 실제 코드에서 첫 번째 방법은 3.81 초가 걸리고 두 번째 방법은 1.88 초가 걸립니다. 좋은 개선이지만 다음과 같습니다.

저는 Python / 스크립트를 사용하는 초보자이며 동일한 작업을 수행하고 처리 시간을 단축하는 더 빠른 방법이 있습니까?

내 응용 프로그램에 대한 더 구체적인 설명 :

Blender API에서 파티클 목록에 액세스 할 수 있습니다.

particles = [1, 2, 3, 4, etc.]

거기서 파티클의 위치에 접근 할 수 있습니다 :

particles[x].location = [x,y,z]

그리고 각 입자에 대해 다음과 같이 각 입자 위치를 검색하여 이웃이 존재하는지 테스트합니다.

if [x+1,y,z] in particles.location
    "Find the identity of this neighbour particle in x:the particle's index
    in the array"
    particles.index([x+1,y,z])

5
파이썬에서 대괄호 안에있는 것은 배열이 아닌 목록이라고합니다. 목록을 사용하는 대신 세트를 사용하십시오. 또는 목록을 정렬하고 bisect모듈을 사용하십시오
Steven Rumbalski

인덱스를 저글링해야합니까? 또는 실제로 주문이 중요하지 않고 회원 선 테스트, 교차로 등을 원하십니까? 즉, 실제로하려는 일에 달려 있습니다. 세트는 당신을 위해 작동 할 수 있습니다, 그리고 그들은 정말 좋은 대답이지만, 우리는 당신이 보여준 코드에서 알 수 없습니다.

2
아마도 당신은 당신의 질문에 가치가 아니라 지수를 명시해야합니다.
Roman Bodnarchuk

나는 내 질문을 편집하고 내가하고 싶은 일을 더 명확하게 설명하려고 노력합니다 ... 그렇게 희망합니다 ...
Jean-Francois Gallant

1
@StevenRumbalski : set은 중복 내용을 포함 할 수 없기 때문에 Jean은 입자의 위치를 ​​저장하려고하지만 (x, y, z는 동일 할 수 있음)이 경우 set을 사용할 수 없습니다
Hieu Vo

답변:


1568
7 in a

가장 명확하고 빠른 방법입니다.

을 사용하는 것도 고려할 수 set있지만 목록에서 해당 세트를 구성하면 멤버쉽 테스트 시간이 단축되는 것보다 시간이 더 걸릴 수 있습니다. 확신 할 수있는 유일한 방법은 벤치 마크를 잘하는 것입니다. (또한 필요한 작업에 따라 다릅니다)


5
그러나 당신은 색인이 없으며 그것을 얻는 것은 당신이 저축 한 비용을 지불 할 것입니다.
rodrigo

6
처럼 : a에서 7 인 경우 : b = a.index (7)?
Jean-Francois Gallant

26
@StevenRumbalski : 세트는 주문할 필요가없는 경우에만 옵션입니다 (따라서 인덱스가 있음). 그리고 세트가 있다 명확하게 대답, 그냥 언급 영업 이익이 질문으로 질문에 대한 직접적인 대답을 제공합니다. 나는 이것이 -1의 가치가 있다고 생각하지 않습니다.

나는 내 질문을 편집하고 내가하고 싶은 일을 더 명확하게 설명하려고 노력합니다 ... 그렇게 희망합니다 ...
Jean-Francois Gallant

1
좋아, 실제 코드에서 메소드를 시도하고 값의 인덱스를 알아야하기 때문에 시간이 조금 더 걸릴 것입니다. 두 번째 방법으로 존재하는지 확인하고 동시에 인덱스를 얻습니다.
Jean-Francois Gallant

212

다른 사람들이 말했듯이 in큰 목록의 경우 속도가 매우 느릴 수 있습니다. in, set및 의 성능을 몇 가지 비교합니다 bisect. 시간 (초)은 로그 스케일입니다.

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

테스트 코드 :

import random
import bisect
import matplotlib.pyplot as plt
import math
import time

def method_in(a,b,c):
    start_time = time.time()
    for i,x in enumerate(a):
        if x in b:
            c[i] = 1
    return(time.time()-start_time)   

def method_set_in(a,b,c):
    start_time = time.time()
    s = set(b)
    for i,x in enumerate(a):
        if x in s:
            c[i] = 1
    return(time.time()-start_time)

def method_bisect(a,b,c):
    start_time = time.time()
    b.sort()
    for i,x in enumerate(a):
        index = bisect.bisect_left(b,x)
        if index < len(a):
            if x == b[index]:
                c[i] = 1
    return(time.time()-start_time)

def profile():
    time_method_in = []
    time_method_set_in = []
    time_method_bisect = []

    Nls = [x for x in range(1000,20000,1000)]
    for N in Nls:
        a = [x for x in range(0,N)]
        random.shuffle(a)
        b = [x for x in range(0,N)]
        random.shuffle(b)
        c = [0 for x in range(0,N)]

        time_method_in.append(math.log(method_in(a,b,c)))
        time_method_set_in.append(math.log(method_set_in(a,b,c)))
        time_method_bisect.append(math.log(method_bisect(a,b,c)))

    plt.plot(Nls,time_method_in,marker='o',color='r',linestyle='-',label='in')
    plt.plot(Nls,time_method_set_in,marker='o',color='b',linestyle='-',label='set')
    plt.plot(Nls,time_method_bisect,marker='o',color='g',linestyle='-',label='bisect')
    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc = 'upper left')
    plt.show()

15
잘라 내기 및 붙여 넣기, 실행 코드를 좋아하십시오. 다른 시간의 몇 초간을 저장하려면, 당신은 세 수입이 필요합니다 : import random / import bisect / import matplotlib.pyplot as plt다음 호출profile()
kghastie

1
어떤 버전의 파이썬입니까?
cowbert

항상 코드를 얻는 것이 좋았지 만 단지 머리를 up습니다. 실행 시간을 가져와야했습니다
whla

겸손한 range()물건을 잊지 마십시오 . 를 사용할 때 객체가 동일한 시퀀스를 모델링 할 수 var in [integer list]있는지 확인하십시오 range(). 세트와 성능이 매우 유사하지만 간결합니다.
Martijn Pieters

37

에 상품을 넣을 수 있습니다 set. 집합 조회는 매우 효율적입니다.

시험:

s = set(a)
if 7 in s:
  # do stuff

편집 의견에서 요소의 색인을 얻고 싶다고 말합니다. 불행히도, 세트에는 요소 위치에 대한 개념이 없습니다. 다른 방법은 목록을 미리 정렬 한 다음 요소를 찾을 때마다 이진 검색 을 사용 하는 것입니다.


그리고 그 후에이 값의 색인을 알고 싶다면 가능하고 빠른 방법이 있습니까?
Jean-Francois Gallant

@ Jean-FrancoisGallant :이 경우 세트는별로 쓸모가 없습니다. 목록을 미리 정렬 한 다음 이진 검색을 사용할 수 있습니다. 업데이트 된 답변을 참조하십시오.
NPE

나는 내 질문을 편집하고 내가하고 싶은 일을 더 명확하게 설명하려고 노력합니다 ... 그렇게 희망합니다 ...
Jean-Francois Gallant

30
def check_availability(element, collection: iter):
    return element in collection

용법

check_availability('a', [1,2,3,4,'a','b','c'])

선택한 값이 배열에 있는지 알 수있는 가장 빠른 방법이라고 생각합니다.


71
return 'a' in a?
Shikiryu

4
def listValue () : a = [1,2,3,4, 'a', 'b', 'c'] ax = listValue ()에서 'a'를 반환 print ( x)
Tenzin

12
유효한 Python 답변이므로 읽기 쉽고 좋은 코드는 아닙니다.
Rick Henderson

1
조심해! 이 경기 이것은 당신이 기대하지 않았다 무엇을 매우 아마 동안 :o='--skip'; o in ("--skip-ias"); # returns True !
알렉스 F

3
@Alex F in연산자는 동일한 방식으로 하위 문자열 멤버쉽을 테스트합니다. 여기서 혼동되는 부분은 아마도 ("hello")단일 값 튜플이 아닌 것입니다 ("hello",). o in ("--skip-ias",)입니다 False예상대로.
MoxieBall

16
a = [4,2,3,1,5,6]

index = dict((y,x) for x,y in enumerate(a))
try:
   a_index = index[7]
except KeyError:
   print "Not found"
else:
   print "found"

이것은 변경되지 않는 경우에만 좋은 아이디어 일 것이므로 dict () 부분을 한 번 수행 한 다음 반복적으로 사용할 수 있습니다. 변경 사항이있는 경우 수행중인 작업에 대한 자세한 정보를 제공하십시오.


내 코드에서 구현 될 때 그것은 작동하지만 것 "형식 오류를 : unhashable 유형을 '목록'
장 - 프랑소와 용감한

1
@ Jean-FrancoisGallant, 아마도 튜플을 사용해야하는 목록을 사용하고 있기 때문일 수 있습니다. 코드 속도를 높이는 방법에 대한 포괄적 인 조언을 원하면 codereview.stackexchange.com에 게시해야합니다. 스타일과 성능 조언을 얻을 수 있습니다.
Winston Ewert

이것은 문제에 대한 매우 영리한 해결책입니다. 구성을 제외하고 try 대신 a_index = index.get (7)을 사용하면 키를 찾을 수 없으면 기본값이 None으로 설정됩니다.
murphsp1

14

원래 질문은 다음과 같습니다.

값이 목록 (수백만 개의 값이 포함 된 목록)에 존재하고 그 색인이 무엇인지 아는 가장 빠른 방법은 무엇입니까?

따라서 찾아야 할 두 가지가 있습니다.

  1. 목록의 항목이며
  2. 색인은 무엇입니까 (목록에있는 경우).

이를 위해 모든 경우에 인덱스를 계산하기 위해 @xslittlegrass 코드를 수정하고 추가 방법을 추가했습니다.

결과

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

방법은 다음과 같습니다.

  1. in-- 기본적으로 x in b 인 경우 : b.index (x)를 반환
  2. b.index (x)에서 try-try / catch (x가 b인지 확인해야 함)
  3. set-- 기본적으로 set (b)의 x 인 경우 : b.index (x)를 반환
  4. bisect-인덱스가있는 b를 정렬하고 sorted (b)에서 x를 이진 검색합니다. @xslittlegrass의 mod는 원래 b가 아닌 정렬 된 b로 인덱스를 반환합니다.
  5. reverse--b에 대한 역방향 조회 사전 d를 형성합니다. d [x]는 x의 인덱스를 제공합니다.

결과는 방법 5가 가장 빠르다는 것을 보여줍니다.

흥미롭게도 tryset 메소드는 시간이 동일합니다.


테스트 코드

import random
import bisect
import matplotlib.pyplot as plt
import math
import timeit
import itertools

def wrapper(func, *args, **kwargs):
    " Use to produced 0 argument function for call it"
    # Reference https://www.pythoncentral.io/time-a-python-function/
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

def method_in(a,b,c):
    for i,x in enumerate(a):
        if x in b:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_try(a,b,c):
    for i, x in enumerate(a):
        try:
            c[i] = b.index(x)
        except ValueError:
            c[i] = -1

def method_set_in(a,b,c):
    s = set(b)
    for i,x in enumerate(a):
        if x in s:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_bisect(a,b,c):
    " Finds indexes using bisection "

    # Create a sorted b with its index
    bsorted = sorted([(x, i) for i, x in enumerate(b)], key = lambda t: t[0])

    for i,x in enumerate(a):
        index = bisect.bisect_left(bsorted,(x, ))
        c[i] = -1
        if index < len(a):
            if x == bsorted[index][0]:
                c[i] = bsorted[index][1]  # index in the b array

    return c

def method_reverse_lookup(a, b, c):
    reverse_lookup = {x:i for i, x in enumerate(b)}
    for i, x in enumerate(a):
        c[i] = reverse_lookup.get(x, -1)
    return c

def profile():
    Nls = [x for x in range(1000,20000,1000)]
    number_iterations = 10
    methods = [method_in, method_try, method_set_in, method_bisect, method_reverse_lookup]
    time_methods = [[] for _ in range(len(methods))]

    for N in Nls:
        a = [x for x in range(0,N)]
        random.shuffle(a)
        b = [x for x in range(0,N)]
        random.shuffle(b)
        c = [0 for x in range(0,N)]

        for i, func in enumerate(methods):
            wrapped = wrapper(func, a, b, c)
            time_methods[i].append(math.log(timeit.timeit(wrapped, number=number_iterations)))

    markers = itertools.cycle(('o', '+', '.', '>', '2'))
    colors = itertools.cycle(('r', 'b', 'g', 'y', 'c'))
    labels = itertools.cycle(('in', 'try', 'set', 'bisect', 'reverse'))

    for i in range(len(time_methods)):
        plt.plot(Nls,time_methods[i],marker = next(markers),color=next(colors),linestyle='-',label=next(labels))

    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc = 'upper left')
    plt.show()

profile()

설명에 오타 ( "역순 루프 업"은 "역방향 조회"여야합니까?)
Cam U

@ CamU-- 예, 수정했습니다. 알아 주셔서 감사합니다.
DarrylG

7

블룸 필터 데이터 구조를 사용하면 응용 프로그램에서 이점을 얻을 수있을 것 같습니다.

간단히 말해서, 블룸 필터 조회는 값이 세트에 확실하게 존재하지 않으면 매우 빨리 알 수 있습니다. 그렇지 않으면 목록에서 POSSIBLY가 될 수있는 값의 인덱스를 얻기 위해 느린 조회를 수행 할 수 있습니다. 따라서 응용 프로그램에서 "찾을 수 없음"결과가 "찾은"결과보다 훨씬 자주 발생하는 경우 블룸 필터를 추가하면 속도가 향상 될 수 있습니다.

자세한 내용은 Wikipedia에서 Bloom Filters의 작동 방식에 대한 훌륭한 개요를 제공하며 "python bloom filter library"에 대한 웹 검색을 통해 유용한 구현을 제공 할 수 있습니다.


7

주의하십시오 in운영자 테스트 평등 (뿐만 아니라 ==)뿐만 아니라 신원 ( is)에 in대한 논리 list의이 거의 비슷 (실제로 최소한의 CPython에서,하지만 파이썬 C와하지 쓰여) 다음 :

for element in s:
    if element is target:
        # fast check for identity implies equality
        return True
    if element == target:
        # slower check for actual equality
        return True
return False

대부분의 경우이 세부 무관하지만, 어떤 상황에서 파이썬 초보자를 떠날 수있는, 예를 들어, 놀라게 numpy.NAN되는 특이한 특성이 그 자체로 동일하고를 :

>>> import numpy
>>> numpy.NAN == numpy.NAN
False
>>> numpy.NAN is numpy.NAN
True
>>> numpy.NAN in [numpy.NAN]
True

이러한 비정상적인 경우를 구별하기 위해 다음 any()과 같이 사용할 수 있습니다 .

>>> lst = [numpy.NAN, 1 , 2]
>>> any(element == numpy.NAN for element in lst)
False
>>> any(element is numpy.NAN for element in lst)
True 

in논리는 다음 listany()같습니다.

any(element is target or element == target for element in lst)

그러나 나는 이것이 최첨단 사례임을 강조해야하며, 대부분의 경우 in운영자는 고도로 최적화되어 있으며 원하는 것은 물론입니다 (또는를 list사용하여 set).


NAN == NAN이 false를 반환하면 특별한 점이 없습니다. IEEE 754 표준에 정의 된 동작입니다.
TommyD

2

또는 사용 __contains__:

sequence.__contains__(value)

데모:

>>> l=[1,2,3]
>>> l.__contains__(3)
True
>>> 

2

@Winston Ewert의 솔루션은 매우 큰 목록의 경우 속도가 크게 향상되지만 스택 오버플로 대답 은 예외 분기에 자주 도달하면 try : / except : / else : 구문이 느려집니다. 대안은 .get()dict 에 대한 방법을 이용하는 것입니다 .

a = [4,2,3,1,5,6]

index = dict((y, x) for x, y in enumerate(a))

b = index.get(7, None)
if b is not None:
    "Do something with variable b"

.get(key, default)방법은 열쇠가 dict에 있다고 보장 할 수없는 경우에만 사용됩니다. 키 있으면 값을 반환 dict[key]하지만 (있는 경우) .get()기본 값을 반환합니다 (여기 None). 이 경우 선택한 기본값이 설정되어 있지 않은지 확인해야합니다.a .


1

이것은 코드가 아니라 매우 빠른 검색을위한 알고리즘입니다.

당신의 목록과 당신이 찾고있는 가치가 모두 숫자라면, 이것은 매우 간단합니다. 문자열 인 경우 : 하단을보십시오.

  • "n"을 목록의 길이로 설정하십시오.
  • -선택적 단계 : 요소 색인이 필요한 경우 : 현재 색인 요소 (0-n-1)로 목록에 두 번째 열 추가-나중에 참조
  • 목록 또는 사본을 주문하십시오 (.sort ())
  • 루프 스루 :
    • 숫자를 목록의 n / 2 번째 요소와 비교
      • 더 큰 경우 인덱스 n / 2-n 사이에서 다시 반복하십시오.
      • 작 으면 인덱스 0-n / 2 사이에서 다시 반복하십시오.
      • 같은 경우 : 당신은 그것을 발견
  • 목록을 찾을 때까지 목록을 좁히거나 두 숫자 만 찾으십시오 (찾고있는 숫자의 위와 아래)
  • 이것은 1.000.000 (log (2) n의 정확한 값) 목록에 대해 최대 19 단계의 요소를 찾습니다.

번호의 원래 위치가 필요한 경우 두 번째 색인 열에서 찾으십시오.

목록이 숫자로 구성되지 않은 경우이 방법은 여전히 ​​작동하며 가장 빠르지 만 문자열을 비교 / 정렬 할 수있는 함수를 정의해야 할 수도 있습니다.

물론 sorted () 메소드에 대한 투자가 필요하지만 확인을 위해 동일한 목록을 계속 재사용하면 가치가 있습니다.


26
설명한 알고리즘이 단순한 이진 검색이라는 것을 언급하지 않았습니다.
diugalde

0

질문이 항상 가장 빠른 기술적 인 방법으로 이해되어서는 안되므로 항상 이해하고 쓸 수있는 가장 간단한 방법을 제안 합니다 :리스트 이해, 한 줄짜리

[i for i in list_from_which_to_search if i in list_to_search_in]

list_to_search_in모든 항목을 가지고 있고 의 항목 색인을 반환하고 싶었습니다 list_from_which_to_search.

좋은 인덱스로 인덱스를 반환합니다.

이 문제를 확인하는 다른 방법이 있습니다. 그러나 목록 이해력은 충분히 빠르기 때문에 문제를 충분히 빨리 작성하여 문제를 해결할 수 있습니다.


-2

저에게는 0.030 초 (실제), 0.026 초 (사용자) 및 0.004 초 (sys)였습니다.

try:
print("Started")
x = ["a", "b", "c", "d", "e", "f"]

i = 0

while i < len(x):
    i += 1
    if x[i] == "e":
        print("Found")
except IndexError:
    pass

-2

곱이 k와 같은 두 요소가 배열에 있는지 확인하는 코드 :

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.