Cách nhanh nhất để kiểm tra nếu một giá trị tồn tại trong danh sách


816

Cách nhanh nhất để biết nếu một giá trị tồn tại trong danh sách (danh sách có hàng triệu giá trị trong đó) và chỉ mục của nó là gì?

Tôi biết rằng tất cả các giá trị trong danh sách là duy nhất như trong ví dụ này.

Phương pháp đầu tiên tôi thử là (3,8 giây trong mã thực của tôi):

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

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

Phương pháp thứ hai tôi thử là (nhanh hơn 2 lần: 1,9 giây cho mã thực của tôi):

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

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

Các phương thức được đề xuất từ ​​người dùng Stack Overflow (2,74 giây cho mã thực của tôi):

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

Trong mã thực của tôi, phương thức đầu tiên mất 3,81 giây và phương thức thứ hai mất 1,88 giây. Đó là một cải tiến tốt, nhưng:

Tôi là người mới bắt đầu với Python / scripting, và có cách nào nhanh hơn để làm những việc tương tự và tiết kiệm thời gian xử lý hơn không?

Giải thích cụ thể hơn cho ứng dụng của tôi:

Trong API Blender tôi có thể truy cập danh sách các hạt:

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

Từ đó, tôi có thể truy cập vị trí của hạt:

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

Và với mỗi hạt tôi kiểm tra xem hàng xóm có tồn tại hay không bằng cách tìm kiếm từng vị trí hạt như vậy:

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
Trong python, điều trong ngoặc vuông được gọi là danh sách, không phải là một mảng. Thay vì sử dụng một danh sách sử dụng một bộ. Hoặc sắp xếp danh sách của bạn và sử dụng bisectmô-đun
Steven Rumbalski

Vì vậy, bạn thực sự cần phải tung hứng các chỉ số? Hoặc không thực sự đặt hàng và bạn chỉ muốn làm các bài kiểm tra tàu, giao lộ, v.v.? Nói theo thứ tự, nó phụ thuộc vào những gì bạn đang thực sự cố gắng làm. Các bộ có thể phù hợp với bạn, và sau đó chúng là một câu trả lời thực sự tốt, nhưng chúng tôi không thể nói từ mã bạn đã hiển thị.

2
Có lẽ bạn phải xác định trong câu hỏi của bạn rằng bạn không cần giá trị, mà là chỉ mục của nó.
La Mã Bodnarchuk

Tôi chỉnh sửa câu hỏi của mình và cố gắng giải thích rõ hơn những gì tôi muốn làm ... Tôi hy vọng vậy ...
Jean-Francois Gallant

1
@StevenRumbalski: vì tập hợp không thể chứa nội dung trùng lặp, trong khi Jean muốn lưu trữ vị trí của các hạt (x, y, z có thể giống nhau), chúng tôi không thể sử dụng cài đặt trong trường hợp này
Hiếu Võ

Câu trả lời:


1572
7 in a

Cách rõ ràng nhất và nhanh nhất để làm điều đó.

Bạn cũng có thể xem xét sử dụng một set, nhưng việc xây dựng được thiết lập từ danh sách của bạn có thể mất nhiều thời gian hơn việc kiểm tra thành viên nhanh hơn sẽ tiết kiệm. Cách duy nhất để chắc chắn là điểm chuẩn tốt. (điều này cũng phụ thuộc vào những hoạt động bạn yêu cầu)


5
Nhưng bạn không có chỉ mục và việc lấy nó sẽ khiến bạn phải trả những gì bạn đã lưu.
Rodrigo

6
như: Nếu 7 trong a: b = a.index (7)?
Jean-Francois Gallant

26
@StevenRumbalski: Các bộ chỉ là một tùy chọn nếu bạn không cần đặt hàng (và do đó, có một chỉ mục). Và các bộ được đề cập rõ ràng trong câu trả lời, nó cũng chỉ đưa ra một câu trả lời đơn giản cho câu hỏi như OP đã hỏi nó. Tôi không nghĩ rằng điều này có giá trị -1.

Tôi chỉnh sửa câu hỏi của mình và cố gắng giải thích rõ ràng hơn những gì tôi muốn làm ... Tôi hy vọng vậy ...
Jean-Francois Gallant

1
Được rồi, tôi thử phương pháp của bạn trong mã thực của tôi và có lẽ sẽ mất thêm một chút thời gian vì tôi cần biết chỉ số của giá trị. Với phương thức thứ hai của tôi, tôi kiểm tra xem nó có tồn tại không và lấy chỉ mục cùng một lúc.
Jean-Francois Gallant

213

Như đã nêu bởi những người khác, incó thể rất chậm cho các danh sách lớn. Dưới đây là một số so sánh của các buổi biểu diễn cho in, setbisect. Lưu ý thời gian (tính bằng giây) là theo tỷ lệ log.

nhập mô tả hình ảnh ở đây

Mã kiểm tra:

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
Yêu cắt và dán, mã thực thi như thế này trong câu trả lời. Để tiết kiệm cho người khác một vài giây, bạn sẽ cần 3 lần nhập: import random / import bisect / import matplotlib.pyplot as pltvà sau đó gọi:profile()
kghastie

1
Phiên bản nào của con trăn này?
cowbert

luôn luôn tuyệt vời để lấy mã nhưng chỉ cần tôi phải nhập thời gian để chạy
từ

Và đừng quên range()đối tượng khiêm tốn . Khi sử dụng var in [integer list], hãy xem liệu một range()đối tượng có thể mô hình hóa cùng một chuỗi. Rất gần trong hiệu suất cho một bộ, nhưng ngắn gọn hơn.
Martijn Pieters

37

Bạn có thể đặt các mặt hàng của bạn vào một set. Đặt tra cứu rất hiệu quả.

Thử:

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

chỉnh sửa Trong một bình luận bạn nói rằng bạn muốn lấy chỉ mục của phần tử. Thật không may, các bộ không có khái niệm về vị trí phần tử. Một cách khác là sắp xếp trước danh sách của bạn và sau đó sử dụng tìm kiếm nhị phân mỗi khi bạn cần tìm một phần tử.


Và nếu sau đó tôi muốn biết chỉ số của giá trị này, điều đó có thể và bạn có cách nào nhanh chóng để làm điều đó không?
Jean-Francois Gallant

@ Jean-FrancoisGallant: Trong trường hợp này, bộ sẽ không được sử dụng nhiều. Bạn có thể sắp xếp trước danh sách và sau đó sử dụng tìm kiếm nhị phân. Xin vui lòng xem câu trả lời cập nhật của tôi.
NPE

Tôi chỉnh sửa câu hỏi của mình và cố gắng giải thích rõ hơn những gì tôi muốn làm ... Tôi hy vọng vậy ...
Jean-Francois Gallant

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

Sử dụng

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

Tôi tin rằng đây là cách nhanh nhất để biết liệu giá trị được chọn có nằm trong một mảng hay không.


71
return 'a' in a?
Shikiryu

4
Bạn cần đặt mã theo định nghĩa: def listValue (): a = [1,2,3,4, 'a', 'b', 'c'] return 'a' in ax = listValue () print ( x)
Tenzin

12
Đó là một câu trả lời Python hợp lệ, nó chỉ là mã không thể đọc được.
Rick Henderson

1
Coi chừng! Điều này phù hợp trong khi đây rất có thể là những gì bạn không mong đợi:o='--skip'; o in ("--skip-ias"); # returns True !
Alex F

3
@Alex F intoán tử hoạt động tương tự để kiểm tra tư cách thành viên chuỗi con. Phần khó hiểu ở đây có lẽ ("hello")không phải là một bộ giá trị đơn, trong khi đó ("hello",)- dấu phẩy tạo ra sự khác biệt. o in ("--skip-ias",)Falsenhư mong đợi.
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"

Đây sẽ chỉ là một ý tưởng tốt nếu không thay đổi và do đó chúng ta có thể thực hiện phần dict () một lần và sau đó sử dụng nó nhiều lần. Nếu có thay đổi, vui lòng cung cấp thêm chi tiết về những gì bạn đang làm.


Nó hoạt động nhưng không phải khi được triển khai trong mã của tôi: "TypeError: loại không thể xóa được: 'list'
Jean-Francois Gallant

1
@ Jean-FrancoisGallant, đó có thể là vì bạn đang sử dụng các danh sách mà bạn thực sự nên sử dụng bộ dữ liệu. Nếu bạn muốn tư vấn toàn diện về cách tăng tốc mã của mình, bạn nên đăng nó tại codereview.stackexchange.com. Ở đó bạn sẽ nhận được lời khuyên về phong cách và hiệu suất.
Winston Ewert

Đây là một giải pháp rất thông minh cho vấn đề. Thay vì thử ngoại trừ cấu trúc, tôi sẽ làm: a_index = index.get (7) sẽ mặc định là Không nếu không tìm thấy khóa.
murphsp1

14

Câu hỏi ban đầu là:

Cách nhanh nhất để biết nếu một giá trị tồn tại trong danh sách (danh sách có hàng triệu giá trị trong đó) và chỉ mục của nó là gì?

Do đó, có hai điều cần tìm:

  1. là một mục trong danh sách, và
  2. chỉ số là gì (nếu trong danh sách).

Hướng tới điều này, tôi đã sửa đổi mã @xslittlegrass để tính toán các chỉ mục trong mọi trường hợp và thêm một phương thức bổ sung.

Các kết quả

nhập mô tả hình ảnh ở đây

Các phương pháp là:

  1. trong - về cơ bản nếu x trong b: return b.index (x)
  2. thử - thử / bắt trên b.index (x) (bỏ qua phải kiểm tra nếu x trong b)
  3. set - về cơ bản nếu x trong set (b): return b.index (x)
  4. bisect - sort b với chỉ mục của nó, tìm kiếm nhị phân cho x trong sort (b). Lưu ý mod từ @xslittlegrass, người trả về chỉ mục trong b được sắp xếp, thay vì b ban đầu
  5. đảo ngược - hình thành một từ điển tra cứu ngược d cho b; thì d [x] cung cấp chỉ số của x.

Kết quả cho thấy phương pháp 5 là nhanh nhất.

Điều thú vị là các phương pháp thửthiết lập là tương đương trong thời gian.


Mã kiểm tra

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

Typo trong mô tả của bạn ("đảo ngược lên" nên là "tra cứu ngược", không?)
Cam U

@ CamU - vâng, đã sửa nó. Cảm ơn đã chú ý.
DarrylG

7

Có vẻ như ứng dụng của bạn có thể đạt được lợi thế từ việc sử dụng cấu trúc dữ liệu Bloom Filter.

Nói tóm lại, việc tra cứu bộ lọc nở có thể cho bạn biết rất nhanh nếu một giá trị KHÔNG HOÀN TOÀN KHÔNG có trong một bộ. Mặt khác, bạn có thể thực hiện tra cứu chậm hơn để có được chỉ số của một giá trị mà POSSIBLY MIGHT BE trong danh sách. Vì vậy, nếu ứng dụng của bạn có xu hướng nhận được kết quả "không tìm thấy" thường xuyên hơn thì kết quả "tìm thấy", bạn có thể thấy tăng tốc bằng cách thêm Bộ lọc Bloom.

Để biết chi tiết, Wikipedia cung cấp một cái nhìn tổng quan tốt về cách thức hoạt động của Bộ lọc Bloom và tìm kiếm trên web cho "thư viện bộ lọc nở hoa trăn" sẽ cung cấp ít nhất một vài triển khai hữu ích.


7

Xin lưu ý rằng intoán tử kiểm tra không chỉ đẳng thức ( ==) mà cả nhận dạng ( is), inlogic listcủa s gần tương đương với sau (thực tế nó được viết bằng C và không phải Python, ít nhất là bằng CPython):

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

Trong hầu hết các trường hợp, chi tiết này không liên quan, nhưng trong một số trường hợp, nó có thể khiến người mới Python ngạc nhiên, chẳng hạn, numpy.NANcó đặc tính bất thường là không bằng chính nó :

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

Để phân biệt giữa những trường hợp bất thường này, bạn có thể sử dụng any() như:

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

Lưu ý inlogic cholist s với any()sẽ là:

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

Tuy nhiên, tôi nên nhấn mạnh rằng đây là trường hợp cạnh và đối với phần lớn các trường hợp, intoán tử được tối ưu hóa cao và chính xác những gì bạn muốn tất nhiên (có thể bằng a listhoặc với a set).


NAN == NAN trả về false không có gì bất thường về nó. Đây là hành vi được xác định trong tiêu chuẩn IEEE 754.
TommyD

2

Hoặc sử dụng __contains__:

sequence.__contains__(value)

Bản giới thiệu:

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

2

@Winston Giải pháp của Ewert mang lại sự tăng tốc lớn cho các danh sách rất lớn, nhưng câu trả lời stackoverflow này chỉ ra rằng việc thử: / trừ: / other: cấu trúc sẽ bị chậm lại nếu thường xuyên đạt được nhánh ngoại trừ. Một cách khác là tận dụng lợi thế của .get()phương thức cho 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"

Các .get(key, default)phương pháp chỉ dành cho trường hợp khi bạn không thể đảm bảo một chìa khóa sẽ nằm trong dict. Nếu khóa hiện tại, nó sẽ trả về giá trị (như sẽ dict[key]), nhưng khi nó không phải là, .get()trả về giá trị mặc định của bạn (ở đây None). Bạn cần đảm bảo trong trường hợp này mặc định đã chọn sẽ không xuất hiện a.


1

Đây không phải là mã, nhưng thuật toán để tìm kiếm rất nhanh.

Nếu danh sách của bạn và giá trị bạn đang tìm kiếm đều là những con số, thì điều này khá đơn giản. Nếu chuỗi: nhìn ở phía dưới:

  • -Nhận "n" là độ dài của danh sách của bạn
  • Bước -Optional: nếu bạn cần chỉ mục của phần tử: thêm cột thứ hai vào danh sách với chỉ mục hiện tại của các phần tử (0 đến n-1) - xem sau
  • Đặt hàng danh sách của bạn hoặc một bản sao của nó (.sort ())
  • Vòng qua:
    • So sánh số của bạn với phần tử thứ 2 của danh sách
      • Nếu lớn hơn, lặp lại giữa các chỉ mục n / 2-n
      • Nếu nhỏ hơn, lặp lại giữa các chỉ mục 0-n / 2
      • Nếu giống nhau: bạn đã tìm thấy nó
  • Tiếp tục thu hẹp danh sách cho đến khi bạn tìm thấy nó hoặc chỉ có 2 số (bên dưới và bên trên số bạn đang tìm kiếm)
  • Điều này sẽ tìm thấy bất kỳ yếu tố nào trong tối đa 19 bước cho danh sách 1.000.000 (log (2) n chính xác)

Nếu bạn cũng cần vị trí ban đầu của số của mình, hãy tìm nó trong cột chỉ mục thứ hai.

Nếu danh sách của bạn không được tạo thành số, phương thức vẫn hoạt động và sẽ nhanh nhất, nhưng bạn có thể cần xác định một hàm có thể so sánh / sắp xếp chuỗi.

Tất nhiên, điều này cần sự đầu tư của phương thức sort (), nhưng nếu bạn tiếp tục sử dụng lại cùng một danh sách để kiểm tra, nó có thể có giá trị.


26
Bạn đã quên đề cập rằng thuật toán bạn giải thích là Tìm kiếm nhị phân đơn giản.
dirifde

0

Bởi vì câu hỏi không phải lúc nào cũng được hiểu là cách kỹ thuật nhanh nhất - tôi luôn đề xuất cách hiểu / viết đơn giản nhất nhanh nhất: hiểu danh sách, một lớp lót

[i for i in list_from_which_to_search if i in list_to_search_in]

Tôi đã có một list_to_search_invới tất cả các mục, và muốn trả về các chỉ mục của các mục trong list_from_which_to_search.

Điều này trả về các chỉ mục trong một danh sách tốt đẹp.

Có nhiều cách khác để kiểm tra vấn đề này - tuy nhiên việc hiểu danh sách là đủ nhanh, thêm vào thực tế viết nó đủ nhanh, để giải quyết vấn đề.


-2

Đối với tôi đó là 0,030 giây (thực), 0,026 giây (người dùng) và 0,004 giây (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

Mã để kiểm tra xem hai phần tử có tồn tại trong mảng có sản phẩm bằng k hay không:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.