Listede bir değerin olup olmadığını kontrol etmenin en hızlı yolu


816

Listede bir değerin (içinde milyonlarca değer içeren bir liste) olup olmadığını ve dizininin ne olduğunu bilmenin en hızlı yolu nedir?

Listedeki tüm değerlerin bu örnekteki gibi benzersiz olduğunu biliyorum.

Denediğim ilk yöntem (gerçek kodumda 3.8 sn):

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

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

Denediğim ikinci yöntem (2 kat daha hızlı: gerçek kodum için 1.9 sn):

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

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

Stack Overflow kullanıcısından önerilen yöntemler (gerçek kodum için 2.74 sn):

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

Gerçek kodumda, ilk yöntem 3,81 saniye ve ikinci yöntem 1,88 saniye sürer. İyi bir gelişme, ama:

Ben Python / script ile yeni başlayan biriyim ve aynı şeyleri yapmanın ve daha fazla işlem süresi kazanmanın daha hızlı bir yolu var mı?

Uygulamam için daha spesifik açıklama:

Blender API'sında bir parçacık listesine erişebilirim:

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

Oradan bir parçacığın konumuna erişebilirim:

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

Ve her bir parçacık için, her bir parçacık konumunu şu şekilde arayarak bir komşu olup olmadığını test ederim:

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
Python'da köşeli parantez içindeki şeye dizi değil liste denir. Bir liste kullanmak yerine bir küme kullanın. Veya listenizi düzenli tutun ve bisectmodülü kullanın
Steven Rumbalski

Yani gerçekten indeksleri dengelemelisin? Yoksa sipariş gerçekten önemli değil ve sadece üye gemi testleri, kavşaklar vb. Yapmak mı istiyorsunuz? Sırayla, gerçekten ne yapmaya çalıştığınıza bağlıdır. Setler sizin için işe yarayabilir ve sonra gerçekten iyi bir cevaptır, ancak gösterdiğiniz koddan anlayamayız.

2
Muhtemelen sorunuzda değere değil, indeksine ihtiyacınız olduğunu belirtmelisiniz.
Roman Bodnarchuk

Sorumu düzenlerim ve ne yapmak istediğimi daha net bir şekilde açıklamaya çalışırım ... Umarım ...
Jean-Francois Gallant

1
@StevenRumbalski: küme çoğaltma içeriği içeremediğinden, Jean parçacıkların konumunu saklamak isterken (x, y, z aynı olabilir), bu durumda
set'i

Yanıtlar:


1568
7 in a

Bunu yapmanın en açık ve en hızlı yolu.

Ayrıca, a'yı kullanmayı da düşünebilirsiniz set, ancak listenizden bu kümeyi oluşturmak, daha hızlı üyelik testinin kaydedeceğinden daha uzun sürebilir. Kesin olmanın tek yolu iyi karşılaştırmaktır. (bu aynı zamanda hangi işlemlere ihtiyacınız olduğuna da bağlıdır)


5
Ama dizine sahip değilsiniz ve bunu elde etmek size kaydettiklerinize mal olacak.
rodrigo

6
like: 7'de bir: b = a.index (7)?
Jean-Francois Gallant

26
@StevenRumbalski: Setler yalnızca sipariş edilmesi gerekmiyorsa (ve dolayısıyla bir indeksiniz varsa) bir seçenektir. Ve setleri edilir açıkça cevap, sadece söz de OP bunu istedi olarak soruya basit bir cevap verir. Bunun -1 değerinde olduğunu sanmıyorum.

Sorumu düzenlerim ve ne yapmak istediğimi daha açık bir şekilde açıklamaya çalışırım ... Umarım ...
Jean-Francois Gallant

1
Tamam, yöntemimi gerçek kodumda deniyorum ve muhtemelen değerin dizinini bilmemiz gerektiğinden biraz daha fazla zaman alıyor. İkinci yöntemimle, var olup olmadığını kontrol ediyorum ve aynı zamanda indeksi alıyorum.
Jean-Francois Gallant

213

Başkaları tarafından belirtildiği gibi, inbüyük listeler için çok yavaş olabilir. İşte için performanslarının bazı karşılaştırmalar vardır in, setve bisect. Sürenin (saniye cinsinden) günlük ölçeğinde olduğunu unutmayın.

resim açıklamasını buraya girin

Test kodu:

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
Kes ve yapıştır, cevaplarda böyle yürütülebilir kodu seviyorum. Başkalarını birkaç saniye kurtarmak için 3 ithalata ihtiyacınız olacak: import random / import bisect / import matplotlib.pyplot as pltve sonra arayın:profile()
kghastie

1
bu hangi python sürümü?
cowbert

kodu almak için her zaman harika ama sadece kafa yukarı Ben çalıştırmak için zaman ithal etmek zorunda kaldı
whla

Ve mütevazı range()nesneyi de unutmayın . Kullanırken var in [integer list], bir range()nesnenin aynı diziyi modelleyip modellemediğine bakın . Performansa bir sete çok yakın, ancak daha özlü.
Martijn Pieters

37

Öğelerinizi bir set. Set aramaları çok verimlidir.

Deneyin:

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

düzenlemek Bir yorumda, öğenin dizinini almak istediğinizi söylersiniz. Ne yazık ki, kümelerin eleman konumu kavramı yoktur. Alternatif olarak, listenizi önceden sıralamak ve her öğe bulmanız gerektiğinde ikili aramayı kullanmaktır .


Ve bundan sonra bu değerin indeksini bilmek istersem mümkün ve bunu yapmanın hızlı bir yolu var mı?
Jean-Francois Gallant

@ Jean-FrancoisGallant: Bu durumda setler pek işe yaramaz. Listeyi önceden sıralayabilir ve ardından ikili aramayı kullanabilirsiniz. Lütfen güncellenmiş cevabıma bakın.
NPE

Sorumu düzenlerim ve ne yapmak istediğimi daha net bir şekilde açıklamaya çalışırım ... Umarım ...
Jean-Francois Gallant

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

kullanım

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

Bu bir dizi seçilen bir değer olup olmadığını bilmek için en hızlı yolu olduğuna inanıyorum.


71
return 'a' in a?
Shikiryu

4
Kodu bir tanıma koymanız gerekir: def listValue (): a = [1,2,3,4, 'a', 'b', 'c'] ax = listValue () print ( x)
Tenzin

12
Bu geçerli bir Python cevabı değil, okunabilir bir kod.
Rick Henderson

1
Dikkat ! Bu muhtemelen beklemediğiniz bir o='--skip'; o in ("--skip-ias"); # returns True !
Alex F

3
@Alex F inoperatörü, alt dize üyeliğini test etmek için aynı şekilde çalışır. Buradaki kafa karıştırıcı kısım muhtemelen ("hello")tek değerli bir demet değil, ("hello",)virgül fark yaratıyor. o in ("--skip-ias",)olduğu Falsebeklendiği gibi.
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"

Bu sadece a değişmezse iyi bir fikir olacaktır ve bu nedenle dict () bölümünü bir kez yapabilir ve daha sonra tekrar tekrar kullanabiliriz. Bir değişiklik olursa, lütfen ne yaptığınızla ilgili daha fazla ayrıntı sağlayın.


Çalışıyor ama benim kodumda uygulandığında değil: "TypeError: unhashable type: 'list'
Jean-Francois Gallant

1
@ Jean-FrancoisGallant, bunun nedeni muhtemelen gerçekten tuples kullanmanız gereken listeleri kullanmanızdır. Kodunuzu nasıl hızlandıracağınız konusunda kapsamlı öneriler almak istiyorsanız, kodu codereview.stackexchange.com adresine göndermeniz gerekir. Orada stil ve performans tavsiyesi alırsınız.
Winston Ewert

Bu, probleme çok zekice bir çözümdür. Yapım dışındaki try yerine, şunu yaparım: a_index = index.get (7).
murphsp1

14

Orijinal soru şuydu:

Listede bir değerin (içinde milyonlarca değer içeren bir liste) olup olmadığını ve dizininin ne olduğunu bilmenin en hızlı yolu nedir?

Bu yüzden bulmak için iki şey vardır:

  1. listedeki bir öğedir ve
  2. dizin nedir (listede varsa).

Bunun için, her durumda dizinleri hesaplamak için @ xslittlegrass kodunu değiştirdim ve ek bir yöntem ekledim.

Sonuçlar

resim açıklamasını buraya girin

Yöntemler:

  1. in - temel olarak b'de x ise: b.index (x) döndürür
  2. dene - b.index (x) üzerinde dene / yakala (b'de x olup olmadığını kontrol etmek zorunda kalır)
  3. set - temel olarak set (b) 'deki x ise: b.index (x) değerini döndürür
  4. bisect - dizini ile sıralama b, sıralanan (b) x için ikili arama. Dizini orijinal b yerine sıralı b'de döndüren @xslittlegrass modunu not edin)
  5. reverse - b için geriye doğru arama sözlüğü oluşturur d; d [x], x dizinini sağlar.

Sonuçlar yöntem 5'in en hızlı olduğunu göstermektedir.

İlginçtir ki, deneme ve ayarlanmış yöntemler zamanla eşdeğerdir.


Test Kodu

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

Açıklamanızdaki yazım hatası ("ters döngü yukarı" "geriye doğru arama," hayır mı?)
Cam U

@ CamU - evet, düzeltildi. Fark ettiğiniz için teşekkürler.
DarrylG

7

Uygulamanızın Bloom Filtre veri yapısının kullanımından fayda sağlayabileceği anlaşılıyor.

Kısacası, bir çiçek filtresi araması, bir değerin bir sette KESİNLİKLE OLMADIĞINI size çok hızlı bir şekilde söyleyebilir. Aksi takdirde, listede OLASI OLABİLECEK bir değerin dizinini almak için daha yavaş bir arama yapabilirsiniz. Uygulamanız "bulunamadı" sonucunu "bulunan" sonuçtan çok daha sık elde etme eğilimindeyse, Bloom Filtresi ekleyerek bir hızlanma görebilirsiniz.

Ayrıntılar için Wikipedia, Bloom Filtrelerinin nasıl çalıştığına dair iyi bir genel bakış sunar ve "python bloom filtre kütüphanesi" için yapılan web araması, en az birkaç faydalı uygulama sağlar.


7

inOperatörün sadece eşitliği ( ==) değil, aynı zamanda kimliği ( is) de test ettiğini unutmayın, s inmantığı kabaca aşağıdakine eşittir (aslında en azından CPython'da Python'da değil, aslında C ile yazılmıştır):list

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

Çoğu durumda bu detay ilgisizdir, ancak bazı durumlarda bir Python acemisini şaşırtabilir, örneğin, numpy.NANkendine eşit olmama alışılmadık bir özelliğe sahiptir :

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

Bu olağandışı durumları ayırt etmek için aşağıdakileri kullanabilirsiniz 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 

Not iniçin mantığı listile s any()olacaktır:

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

Bununla birlikte, bunun bir uç durum olduğunu vurgulamalıyım ve vakaların büyük çoğunluğu için inoperatör son derece optimize edilmiştir ve tam olarak ne istediğinizi (a listveya a ile set).


NAN == NAN yanlış döndürürken bu konuda alışılmadık bir şey yok. IEEE 754 standardında tanımlanan davranıştır.
TommyD

2

Veya şunu kullanın __contains__:

sequence.__contains__(value)

Demo:

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

2

@Winston Ewert'in çözümü çok büyük listeler için büyük bir hızlanma sağlar, ancak bu yığın akışı cevabı , try / / hariç: / else: yapısının, dış dal sık sık ulaşılırsa yavaşlayacağını gösterir. Bir alternatif, söz konusu .get()yöntemden yararlanmaktır :

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)Yöntem sadece bir anahtar dict olacağını garanti edemez durumda içindir. Anahtar ise ise mevcut, o (gibi değer döndürür dict[key]), ancak tersi geçerli olduğunda, .get()varsayılan değer (burada döner None). Bu durumda, seçilen varsayılan ayarın girilmeyeceğinden emin olmanız gerekir a.


1

Bu kod değil, çok hızlı arama algoritmasıdır.

Listeniz ve aradığınız değer tüm sayılarsa, bu oldukça basittir. Dizeler ise: aşağıya bakın:

  • -Leterinizin uzunluğu "n" olsun
  • -İsteğe bağlı adım: öğenin dizinine ihtiyacınız varsa: listeye geçerli öğe diziniyle (0 - n-1) ikinci bir sütun ekleyin - daha sonra bakın
  • Listenizi veya bir kopyasını sipariş edin (.sort ())
  • Döngü:
    • Numaranızı listenin n / 2. elementiyle karşılaştırın
      • Daha büyükse, n / 2-n dizinleri arasında tekrar döngü
      • Daha küçükse, 0-n / 2 dizinleri arasında tekrar döngü yapın
      • Eğer aynıysa: buldun
  • Listeyi bulana kadar daraltmaya devam edin veya yalnızca 2 numara (aradığınızın altında ve üstünde)
  • Bu , 1.000.000 (log (2) n'nin kesin olması) listesi için en fazla 19 adımda herhangi bir öğeyi bulur.

Ayrıca numaranızın orijinal konumuna ihtiyacınız varsa, ikinci dizin sütununda arayın.

Listeniz sayılardan oluşmuyorsa, yöntem yine de çalışır ve en hızlı olacaktır, ancak dizeleri karşılaştırabilen / sıralayabilen bir işlev tanımlamanız gerekebilir.

Tabii ki, bu, sort () yönteminin yatırımına ihtiyaç duyar, ancak aynı listeyi kontrol için tekrar kullanmaya devam ederseniz, buna değebilir.


26
Açıkladığınız algoritmanın basit bir İkili Arama olduğunu belirtmeyi unuttunuz.
diugalde

0

Sorunun her zaman en hızlı teknik yol olarak anlaşılmaması gerektiği için - Anlamak / yazmak için her zaman en basit en hızlı yolu öneririm : bir liste kavraması, tek satırlı

[i for i in list_from_which_to_search if i in list_to_search_in]

Ben list_to_search_intüm öğeleri ile vardı ve öğelerin indeksleri dönmek istedim list_from_which_to_search.

Bu, güzel bir listedeki dizinleri döndürür.

Bu sorunu kontrol etmenin başka yolları da vardır - ancak liste kavrayışları yeterince hızlıdır, bir sorunu çözmek için yeterince hızlı yazma gerçeğine katkıda bulunur.


-2

Benim için 0.030 saniye (gerçek), 0.026 saniye (kullanıcı) ve 0.004 saniye (sys) idi.

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

Ürününde k değerine eşit olan iki öğenin var olup olmadığını kontrol etmek için kod:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.