Найшвидший спосіб перевірити, чи існує значення у списку


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"

Запропоновані методи від користувача переповнення стека (2,74 сек для мого реального коду):

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

У моєму реальному коді перший метод займає 3,81 сек, а другий - 1,88 сек. Це гарне поліпшення, але:

Я початківець з Python / scripting, і чи є більш швидкий спосіб зробити те ж саме і заощадити більше часу на обробку?

Більш конкретне пояснення для моєї заявки:

В API Blender я можу отримати доступ до списку частинок:

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
У python річ у квадратних дужках називається списком, а не масивом. Замість використання списку використовуйте набір. Або тримайте свій список відсортованим та використовуйте bisectмодуль
Стівен Румбальський

Тож вам дійсно потрібно жонглювати індексами? Або насправді замовлення не має значення, і ви просто хочете зробити випробування суден-членів, перехрестя тощо? У порядку слова, це залежить від того, що ви насправді намагаєтеся зробити. Набори можуть працювати для вас, і тоді вони справді хороша відповідь, але ми не можемо сказати з показаного вами коду.

2
Ймовірно, вам слід вказати у своєму питанні, що вам потрібно не значення, а його індекс.
Роман Боднарчук

Я редагую своє запитання і намагаюся більш чітко пояснити, що я хочу зробити ... Сподіваюся, що так ...
Жан-Франсуа Галант

1
@StevenRumbalski: оскільки набір не може містити вміст дублювання, а Жан хоче зберігати розташування частинок (x, y, z може бути однаковим), ми не можемо використовувати набір у цьому випадку
Hieu Vo

Відповіді:


1568
7 in a

Найясніший і найшвидший спосіб зробити це.

Ви також можете розглянути можливість використання set, але побудова цього набору зі списку може зайняти більше часу, ніж швидше тестування членства заощадить. Єдиний спосіб бути впевненим - це добре орієнтуватися. (це також залежить від необхідних операцій)


5
Але індексу у вас немає, і отримання його обійдеться вам за те, що ви зберегли.
rodrigo

6
як: Якщо 7 в a: b = a.index (7)?
Жан-Франсуа Галант

26
@StevenRumbalski: Набори - це лише варіант, якщо вам не потрібно його замовляти (а значить, мати індекс). І набори будуть чітко згадується у відповіді, це просто також дає пряму відповідь на питання , як ОП запитав його. Я не думаю, що це вартує -1.

Я редагую своє запитання і намагаюсь більш чітко пояснити, що я хочу зробити ... Сподіваюся, що так ...
Жан-Франсуа Галант

1
Гаразд, я спробую ваш метод у моєму реальному коді, і це займе трохи більше часу, ймовірно, тому що мені потрібно знати індекс значення. Своїм другим методом я перевіряю, чи він існує, і отримую індекс одночасно.
Жан-Франсуа Галант

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
Любіть вирізати і вставляти, виконуваний код, як це у відповідях. Щоб заощадити інших на кілька секунд часу, вам знадобиться 3 імпорту: import random / import bisect / import matplotlib.pyplot as pltа потім зателефонуйте:profile()
kghastie

1
яка версія python це?
коуберт

завжди чудово, щоб отримати код, але просто вгору, мені довелося імпортувати час для запуску
whla

І не забувайте про скромний range()предмет. Під час використання var in [integer list]дивіться, чи range()може об'єкт моделювати ту саму послідовність. Дуже близький у виконанні до набору, але більш стислий.
Martijn Pieters

37

Ви можете помістити свої предмети в set. Набори пошуку дуже ефективні.

Спробуйте:

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

редагувати У коментарі ви говорите, що хочете отримати індекс елемента. На жаль, множини не мають поняття положення елементів. Альтернативою є попереднє сортування списку, а потім використання двійкового пошуку кожного разу, коли вам потрібно знайти елемент.


І якщо після цього я хочу знати індекс цього значення, можливо, і у вас є швидкий спосіб це зробити?
Жан-Франсуа Галант

@ Jean-FrancoisGallant: У цьому випадку набори не принесуть великої користі. Ви можете заздалегідь сортувати список та використовувати двійковий пошук. Будь ласка, дивіться мою оновлену відповідь.
NPE

Я редагую своє запитання і намагаюся більш чітко пояснити, що я хочу зробити ... Сподіваюся, що так ...
Жан-Франсуа Галант

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?
Шикірю

4
Вам потрібно покласти код у визначення: def listValue (): a = [1,2,3,4, 'a', 'b', 'c'] return 'a' в ax = listValue () print ( x)
Тензін

12
Це дійсна відповідь Python, це просто не добре, читабельний код.
Рік Хендерсон

1
Остерігайся! Це збіги, хоча це, мабуть, те, чого ви не очікували:o='--skip'; o in ("--skip-ias"); # returns True !
Alex 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 () один раз, а потім використати її повторно. Якщо зміна зміниться, будь ласка, надайте більш детальну інформацію про те, що ви робите.


Він працює, але не застосовується у моєму коді: "TypeError: unhashable type: 'list"
Жан-Франсуа Галант

1
@ Jean-FrancoisGallant, це, мабуть, тому, що ви використовуєте списки, де ви справді повинні використовувати кортежі. Якщо ви хочете вичерпну пораду щодо прискорення коду, слід опублікувати його на codereview.stackexchange.com. Там ви отримаєте поради щодо стилю та виконання.
Вінстон Еверт

Це дуже розумне рішення проблеми. Замість спроби, крім конструкції, я б зробив: a_index = index.get (7), який за замовчуванням буде значення None, якщо ключ не знайдено.
murphsp1

14

Первісне питання:

Який найшвидший спосіб дізнатися, чи існує в списку значення (список з мільйонами значень) і який його індекс?

Таким чином, слід знайти дві речі:

  1. є елементом у списку, і
  2. який індекс (якщо в списку).

Назустріч цьому я змінив код @xslittlegrass для обчислення індексів у всіх випадках і додав додатковий метод.

Результати

введіть тут опис зображення

Методи:

  1. в - в основному, якщо x в b: повернути b.index (x)
  2. спробувати - спробувати / ловити на b.index (x) (пропускає, щоб перевірити, чи x in b)
  3. set - в основному, якщо x у множині (b): повернути b.index (x)
  4. bisect - сортувати b з його індексом, двійковий пошук x у сортованому (b). Зауважте мод від @xslittlegrass, який повертає індекс у відсортованому b, а не в оригінальному b)
  5. reverse - формувати словник зворотного пошуку d для b; тоді d [x] забезпечує індекс x.

Результати показують, що метод 5 найшвидший.

Цікаво, що спроби та встановлені методи еквівалентні за часом.


Код тесту

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

Здається, що ваша програма може отримати перевагу від використання структури даних фільтра Bloom.

Коротше кажучи, пошук фільтра цвітіння може сказати вам дуже швидко, якщо значення ВИЗНАЧЕНО НЕ присутнє в наборі. В іншому випадку ви можете зробити більш повільний пошук, щоб отримати індекс значення, МОЖЛИВО МОЖУТЬ бути у списку. Отже, якщо ваша програма має тенденцію до отримання результату "не знайдено" набагато частіше, ніж результату "знайдено", ви можете побачити швидкість, додавши фільтр Bloom.

Для детальної інформації Вікіпедія надає хороший огляд того, як працюють фільтри Bloom, а веб-пошук "бібліотеки фільтрів пітонного цвіту" забезпечить принаймні пару корисних реалізацій.


7

Майте на увазі, що inоператор перевіряє не тільки рівність ( ==), але й ідентичність ( is), inлогіка для lists приблизно еквівалентна наступному (це насправді написано на C, а не на Python, хоча принаймні в 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

У більшості обставин ця деталь не має значення, але в деяких обставинах новачок Python може здивуватися, наприклад, 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логіка для lists з any()буде такою:

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

Однак я мушу підкреслити, що це кращий випадок, і в переважній більшості випадків inоператор дуже оптимізований і саме те, що ви хочете, звичайно (або з a, listабо з a set).


NAN == NAN, що повертає false, не має нічого незвичайного. Це поведінка, визначена стандартом IEEE 754.
TommyD

2

Або скористайтеся __contains__:

sequence.__contains__(value)

Демонстрація:

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

2

Рішення @Winston Ewert дає велике прискорення для дуже великих списків, але ця відповідь stackoverflow вказує на те, що конструкція try: / крім: / else: буде сповільнена, якщо часто досягається крім гілки. Альтернативою є використання .get()методу дикту:

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[key]), але коли це не так , .get()повертає ваше значення по замовчуванням (тут None). У цьому випадку вам потрібно переконатися, що вибраний за замовчуванням не буде a.


1

Це не код, а алгоритм дуже швидкого пошуку.

Якщо ваш список і цінність, яку ви шукаєте, - це всі цифри, це досить просто. Якщо рядки: подивіться внизу:

  • -Нехай "n" буде довжиною вашого списку
  • -Необов’язковий крок: якщо вам потрібен індекс елемента: додайте до списку другий стовпець із поточним індексом елементів (0 до n-1) - див. Пізніше
  • Замовте свій список або його копію (.sort ())
  • Проведіть цикл через:
    • Порівняйте свій номер з n / 2-м елементом списку
      • Якщо більше, повторіть цикл між індексами n / 2-n
      • Якщо менше, повторіть цикл між індексами 0-n / 2
      • Якщо те саме: ви його знайшли
  • Продовжуйте звужувати список, поки ви його не знайдете або матимете лише 2 номери (нижче та вище того, кого шукаєте)
  • Це знайде будь-який елемент, щонайменше, у 19 кроках для списку 1.000.000 (журнал (2) n, щоб бути точним)

Якщо вам також потрібна вихідна позиція вашого номера, знайдіть його у другому стовпчику індексу.

Якщо ваш список не складається з чисел, метод все ще працює і буде найшвидшим, але вам може знадобитися визначити функцію, яка може порівнювати / упорядковувати рядки.

Звичайно, для цього потрібна інвестиція методу sorted (), але якщо ви повторно використовуєте той самий список для перевірки, можливо, це варто.


26
Ви забули згадати, що алгоритм, який ви пояснили, - це простий двійковий пошук.
діугальде

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.