Cara tercepat untuk memeriksa apakah ada nilai dalam daftar

817

Apa cara tercepat untuk mengetahui apakah ada nilai dalam daftar (daftar dengan jutaan nilai di dalamnya) dan apa indeksnya?

Saya tahu bahwa semua nilai dalam daftar adalah unik seperti dalam contoh ini.

Metode pertama yang saya coba adalah (3,8 detik dalam kode asli saya):

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

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

Metode kedua yang saya coba adalah (2x lebih cepat: 1,9 detik untuk kode asli saya):

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

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

Metode yang diusulkan dari pengguna Stack Overflow (2,74 detik untuk kode asli saya):

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

Dalam kode asli saya, metode pertama membutuhkan 3,81 detik dan metode kedua membutuhkan 1,88 detik. Ini peningkatan yang bagus, tetapi:

Saya seorang pemula dengan Python / scripting, dan apakah ada cara yang lebih cepat untuk melakukan hal yang sama dan menghemat lebih banyak waktu pemrosesan?

Penjelasan lebih spesifik untuk aplikasi saya:

Di Blender API saya bisa mengakses daftar partikel:

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

Dari sana, saya dapat mengakses lokasi partikel:

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

Dan untuk setiap partikel saya menguji apakah ada tetangga dengan mencari setiap lokasi partikel seperti:

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])
Jean-Francois Gallant
sumber
5
Dalam python, benda dalam tanda kurung siku disebut daftar, bukan array. Daripada menggunakan daftar, gunakan satu set. Atau simpan daftar Anda diurutkan dan gunakan bisectmodul
Steven Rumbalski
Jadi Anda benar-benar perlu menyulap indeks? Atau tidak memesan sebenarnya penting dan Anda hanya ingin melakukan tes kapal anggota, persimpangan, dll? Dengan kata lain, itu tergantung pada apa yang sebenarnya Anda coba lakukan. Set mungkin bekerja untuk Anda, dan kemudian itu adalah jawaban yang sangat bagus, tetapi kami tidak dapat mengatakan dari kode yang Anda perlihatkan.
2
Mungkin Anda harus menentukan dalam pertanyaan Anda bahwa Anda tidak perlu nilainya, tetapi indeksnya.
Roman Bodnarchuk
Saya mengedit pertanyaan saya dan mencoba menjelaskan dengan lebih jelas apa yang ingin saya lakukan ... Saya harap begitu ...
Jean-Francois Gallant
1
@StevenRumbalski: karena set tidak dapat berisi konten duplikasi, sementara Jean ingin menyimpan lokasi partikel (x, y, z bisa sama), kita tidak bisa menggunakan set dalam kasus ini
Hieu Vo

Jawaban:

1573
7 in a

Cara paling jelas dan tercepat untuk melakukannya.

Anda juga dapat mempertimbangkan untuk menggunakan set, tetapi membuat set dari daftar Anda itu mungkin memakan waktu lebih lama daripada yang akan menghemat pengujian keanggotaan. Satu-satunya cara untuk memastikan adalah melakukan benchmark dengan baik. (ini juga tergantung pada operasi apa yang Anda butuhkan)

Rafe Kettler
sumber
5
Tetapi Anda tidak memiliki indeks, dan mendapatkannya akan dikenakan biaya apa yang Anda tabung.
rodrigo
6
seperti: Jika 7 dalam a: b = a.index (7)?
Jean-Francois Gallant
26
@StevenRumbalski: Set hanya pilihan jika Anda tidak perlu memesan (dan karenanya, memiliki indeks). Dan set yang jelas disebutkan dalam jawabannya, itu hanya juga memberi jawaban mudah untuk pertanyaan seperti OP bertanya itu. Saya tidak berpikir ini bernilai -1.
Saya mengedit pertanyaan saya dan mencoba menjelaskan dengan lebih jelas apa yang ingin saya lakukan ... Saya harap begitu ...
Jean-Francois Gallant
1
Oke, saya mencoba metode Anda dalam kode asli saya dan mungkin butuh sedikit lebih banyak waktu mungkin karena saya perlu tahu indeks nilainya. Dengan metode kedua saya, saya memeriksa apakah ada dan mendapatkan indeks pada saat yang sama.
Jean-Francois Gallant
213

Seperti yang dinyatakan oleh orang lain, inbisa sangat lambat untuk daftar besar. Berikut adalah beberapa perbandingan pertunjukan untuk in, setdan bisect. Perhatikan waktu (dalam detik) dalam skala log.

masukkan deskripsi gambar di sini

Kode untuk pengujian:

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()
xslittlegrass
sumber
15
Sukai cut-and-paste, kode yang dapat dieksekusi seperti ini dalam jawaban. Untuk menghemat waktu beberapa detik, Anda akan membutuhkan 3 impor: import random / import bisect / import matplotlib.pyplot as pltlalu hubungi:profile()
kghastie
1
versi python apakah ini?
cowbert
selalu hebat untuk mendapatkan kode tetapi hanya kepala saya harus mengimpor waktu untuk menjalankan
whla
Dan jangan lupa range()objek yang sederhana . Saat menggunakan var in [integer list], lihat apakah suatu range()objek dapat memodelkan urutan yang sama. Sangat dekat kinerjanya dengan satu set, tetapi lebih ringkas.
Martijn Pieters
37

Anda bisa memasukkan barang Anda ke dalam set. Pengaturan pencarian sangat efisien.

Mencoba:

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

sunting Di komentar, Anda mengatakan ingin mendapatkan indeks elemen. Sayangnya, set tidak memiliki gagasan tentang posisi elemen. Alternatifnya adalah dengan melakukan pre-sorting daftar Anda dan kemudian menggunakan pencarian biner setiap kali Anda perlu menemukan elemen.

NPE
sumber
Dan jika setelah itu saya ingin mengetahui indeks nilai ini, apakah mungkin dan Anda memiliki cara cepat untuk melakukannya?
Jean-Francois Gallant
@ Jean-FrancoisGallant: Dalam hal ini set tidak akan banyak berguna. Anda bisa menyortir daftar dan kemudian menggunakan pencarian biner. Silakan lihat jawaban saya yang diperbarui.
NPE
Saya mengedit pertanyaan saya dan mencoba menjelaskan dengan lebih jelas apa yang ingin saya lakukan ... Saya harap begitu ...
Jean-Francois Gallant
30
def check_availability(element, collection: iter):
    return element in collection

Pemakaian

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

Saya percaya ini adalah cara tercepat untuk mengetahui apakah nilai yang dipilih ada dalam array.

Tiago Moutinho
sumber
71
return 'a' in a?
Shikiryu
4
Anda perlu memasukkan kode dalam definisi: def listValue (): a = [1,2,3,4, 'a', 'b', 'c'] mengembalikan 'a' di ax = listValue () cetak ( x)
Tenzin
12
Ini jawaban Python yang valid. Hanya saja kode itu tidak baik dan mudah dibaca.
Rick Henderson
1
Waspadalah! Ini cocok sementara ini sangat mungkin apa yang tidak Anda harapkan:o='--skip'; o in ("--skip-ias"); # returns True !
Alex F
3
@Alex F inoperator bekerja dengan cara yang sama untuk menguji keanggotaan substring. Bagian yang membingungkan di sini mungkin ("hello")bukan tuple bernilai tunggal, sementara ("hello",)- koma yang membuat perbedaan. o in ("--skip-ias",)adalah Falseseperti yang diharapkan.
MoxieBall
17
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"

Ini hanya akan menjadi ide yang baik jika a tidak berubah dan dengan demikian kita dapat melakukan bagian dict () sekali dan kemudian menggunakannya berulang kali. Jika a memang berubah, harap berikan detail lebih lanjut tentang apa yang Anda lakukan.

Winston Ewert
sumber
Ini berfungsi tetapi tidak ketika diimplementasikan dalam kode saya: "TypeError: tipe yang tidak dapat ditembus: 'list'
Jean-Francois Gallant
1
@ Jean-FrancoisGallant, itu mungkin karena Anda menggunakan daftar di mana Anda seharusnya menggunakan tuple. Jika Anda ingin saran komprehensif tentang cara mempercepat kode Anda, Anda harus mempostingnya di codereview.stackexchange.com. Di sana Anda akan mendapatkan saran gaya dan kinerja.
Winston Ewert
1
Ini adalah solusi yang sangat cerdas untuk masalah ini. Alih-alih mencoba kecuali membangun, saya akan melakukan: a_index = index.get (7) yang akan default ke Tidak ada jika kunci tidak ditemukan.
murphsp1
14

Pertanyaan aslinya adalah:

Apa cara tercepat untuk mengetahui apakah ada nilai dalam daftar (daftar dengan jutaan nilai di dalamnya) dan apa indeksnya?

Jadi ada dua hal yang harus dicari:

  1. adalah item dalam daftar, dan
  2. apa indeksnya (jika ada dalam daftar).

Terhadap ini, saya memodifikasi kode @xslittlegrass untuk menghitung indeks dalam semua kasus, dan menambahkan metode tambahan.

Hasil

masukkan deskripsi gambar di sini

Metode adalah:

  1. in - pada dasarnya jika x in b: return b.index (x)
  2. coba - coba / tangkap di b.index (x) (melompati harus memeriksa apakah x dalam b)
  3. set - pada dasarnya jika x dalam set (b): return b.index (x)
  4. membagi dua - urut b dengan indeksnya, pencarian biner untuk x diurutkan (b). Catat mod dari @xslittlegrass yang mengembalikan indeks dalam b yang diurutkan, daripada yang asli b)
  5. terbalik - bentuk kamus pencarian terbalik d untuk b; lalu d [x] memberikan indeks x.

Hasil menunjukkan bahwa metode 5 adalah yang tercepat.

Menariknya, coba dan metode yang ditetapkan setara dalam waktu.


Kode Uji

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()
DarrylG
sumber
Mengetik dalam deskripsi Anda ("loop terbalik ke atas" harus "reverse lookup," tidak?)
Cam U
@ CamU - ya, perbaiki. Terima kasih telah memperhatikan.
DarrylG
7

Sepertinya aplikasi Anda mungkin mendapatkan keuntungan dari penggunaan struktur data Bloom Filter.

Singkatnya, pencarian filter bloom dapat memberi tahu Anda dengan sangat cepat jika nilainya TIDAK PASTI hadir dalam satu set. Jika tidak, Anda dapat melakukan pencarian lebih lambat untuk mendapatkan indeks dari nilai yang MUNGKIN MENJADI dalam daftar. Jadi jika aplikasi Anda cenderung mendapatkan hasil "tidak ditemukan" lebih sering daripada hasil "ditemukan", Anda mungkin melihat percepatan dengan menambahkan Bloom Filter.

Untuk detail, Wikipedia memberikan tinjauan yang baik tentang cara kerja Bloom Filter, dan pencarian web untuk "python bloom filter library" akan menyediakan setidaknya beberapa implementasi yang bermanfaat.

matt2000
sumber
7

Ketahuilah bahwa inoperator menguji tidak hanya persamaan ( ==) tetapi juga identitas ( is), inlogika untuk lists kira - kira setara dengan yang berikut (sebenarnya ditulis dalam C dan bukan Python, setidaknya dalam 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

Dalam sebagian besar keadaan, detail ini tidak relevan, tetapi dalam beberapa keadaan mungkin membuat pemula Python terkejut, misalnya, numpy.NANmemiliki properti yang tidak biasa yaitu tidak sama dengan dirinya sendiri :

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

Untuk membedakan antara kasus-kasus yang tidak biasa ini, Anda dapat menggunakan any()seperti:

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

Perhatikan bahwa inlogika untuk lists any()adalah:

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

Namun, saya harus menekankan bahwa ini adalah kasus tepi, dan untuk sebagian besar kasus, inoperator sangat dioptimalkan dan tentu saja apa yang Anda inginkan (baik dengan a listatau dengan a set).

Chris_Rands
sumber
NAN == NAN mengembalikan false tidak ada yang aneh tentang hal itu. Ini adalah perilaku yang didefinisikan dalam standar IEEE 754.
TommyD
2

Atau gunakan __contains__:

sequence.__contains__(value)

Demo:

>>> l=[1,2,3]
>>> l.__contains__(3)
True
>>> 
U10-Maju
sumber
2

Solusi @Winston Ewert menghasilkan percepatan besar untuk daftar yang sangat besar, tetapi jawaban stackoverflow ini menunjukkan bahwa coba: / kecuali: / lain: konstruk akan melambat jika cabang kecuali sering dicapai. Alternatifnya adalah memanfaatkan .get()metode untuk dikt:

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"

The .get(key, default)Metode ini hanya untuk kasus ketika Anda tidak dapat menjamin kunci akan di dict. Jika kunci adalah hadir, ia mengembalikan nilai (seperti yang akan dict[key]), tetapi jika tidak, .get()mengembalikan nilai default (di sini None). Anda harus memastikan dalam hal ini bahwa default yang dipilih tidak akan masuk a.

pengguna3897315
sumber
1

Ini bukan kode, tetapi algoritma untuk pencarian yang sangat cepat.

Jika daftar Anda dan nilai yang Anda cari semuanya angka, ini cukup mudah. Jika string: lihat bagian bawah:

  • -Biarkan "n" menjadi panjang daftar Anda
  • -Langkah opsional: jika Anda membutuhkan indeks elemen: tambahkan kolom kedua ke daftar dengan indeks elemen saat ini (0 hingga n-1) - lihat nanti
  • Pesan daftar Anda atau salinannya (.sort ())
  • Loop melalui:
    • Bandingkan nomor Anda dengan elemen ke-2 dari daftar
      • Jika lebih besar, lingkaran lagi di antara indeks n / 2-n
      • Jika lebih kecil, lingkaran lagi di antara indeks 0-n / 2
      • Jika sama: Anda menemukannya
  • Tetap persempit daftar sampai Anda menemukannya atau hanya memiliki 2 angka (di bawah dan di atas yang Anda cari)
  • Ini akan menemukan elemen apa pun dalam paling banyak 19 langkah untuk daftar 1.000.000 (log (2) n tepatnya)

Jika Anda juga membutuhkan posisi asli nomor Anda, cari di kolom indeks kedua.

Jika daftar Anda tidak terbuat dari angka, metode ini masih berfungsi dan akan menjadi yang tercepat, tetapi Anda mungkin perlu mendefinisikan fungsi yang dapat membandingkan / memesan string.

Tentu saja, ini membutuhkan investasi dari metode disortir (), tetapi jika Anda terus menggunakan kembali daftar yang sama untuk memeriksa, mungkin layak dilakukan.

Adam
sumber
26
Anda lupa menyebutkan bahwa algoritma yang Anda jelaskan adalah Pencarian Biner sederhana.
diugalde
0

Karena pertanyaannya tidak selalu harus dipahami sebagai cara teknis tercepat - saya selalu menyarankan cara tercepat yang paling mudah untuk memahami / menulis: pemahaman daftar, one-liner

[i for i in list_from_which_to_search if i in list_to_search_in]

Saya punya list_to_search_indengan semua item, dan ingin mengembalikan indeks item di list_from_which_to_search.

Ini mengembalikan indeks dalam daftar yang bagus.

Ada cara lain untuk memeriksa masalah ini - namun daftar pemahamannya cukup cepat, menambah fakta menulisnya cukup cepat, untuk menyelesaikan masalah.

Vaidøtas Ivøška
sumber
-2

Bagi saya itu adalah 0,030 detik (nyata), 0,026 detik (pengguna), dan 0,004 detik (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
Tabin1000
sumber
-2

Kode untuk memeriksa apakah ada dua elemen dalam array yang produknya sama dengan k:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
ravi tanwar
sumber