Самый быстрый способ проверить, существует ли значение в списке

820

Какой самый быстрый способ узнать, существует ли значение в списке (список с миллионами значений в нем) и каков его индекс?

Я знаю, что все значения в списке уникальны, как в этом примере.

Первый метод, который я пробую, - это (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 / scripting, и есть ли более быстрый способ сделать то же самое и сэкономить больше времени на обработку?

Более конкретное объяснение для моего приложения:

В 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
В python вещь в квадратных скобках называется списком, а не массивом. Вместо использования списка используйте набор. Или сохраните ваш список отсортированным и используйте bisectмодуль
Стивен Румбальски
Так вам действительно нужно манипулировать индексами? Или порядок на самом деле не имеет значения, и вы просто хотите провести тестирование, пересечение и т.д. Другими словами, это зависит от того, что вы действительно пытаетесь сделать. Наборы могут работать на вас, и тогда они действительно хороший ответ, но мы не можем сказать по коду, который вы показали.
2
Возможно, вы должны указать в своем вопросе, что вам нужно не значение, а его индекс.
Роман Боднарчук
Я редактирую свой вопрос и пытаюсь объяснить более четко, что я хочу сделать ... Я надеюсь на это ...
Жан-Франсуа Галлан
1
@StevenRumbalski: так как набор не может содержать дублирующееся содержимое, в то время как Джин хочет сохранить местоположение частиц (x, y, z могут быть одинаковыми), мы не можем использовать набор в этом случае
Hieu Vo

Ответы:

1577
7 in a

Самый простой и быстрый способ сделать это.

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

Рэйф Кеттлер
источник
5
Но у вас нет индекса, и его получение будет стоить того, что вы сэкономили.
Родриго
6
как: если 7 в a: b = a.index (7)?
Жан-Франсуа Галант
26
@StevenRumbalski: Наборы являются опцией, только если вам не нужно упорядочивать их (и, следовательно, иметь индекс). И наборы будут четко упоминается в ответе, это просто также дает прямой ответ на вопрос , как ОП спросил его. Я не думаю, что это стоит -1.
Я редактирую свой вопрос и пытаюсь объяснить более четко, что я хочу сделать ... Надеюсь, что так ...
Жан-Франсуа Галлан
1
Хорошо, я попробую ваш метод в своем реальном коде, и это займет немного больше времени, вероятно, потому что мне нужно знать индекс значения. Вторым методом я проверяю, существует ли он, и одновременно получаю индекс.
Жан-Франсуа Галант
213

Как утверждают другие, 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()
xslittlegrass
источник
15
Любите вырезать и вставить, исполняемый код, как это в ответах. Чтобы сэкономить другим несколько секунд времени, вам нужно 3 импорта: import random / import bisect / import matplotlib.pyplot as pltи затем позвоните:profile()
kghastie
1
какая версия Python это?
Cowbert
всегда здорово получить код, но просто хедз-ап мне пришлось импортировать время для запуска
whla
И не забывайте, скромный range()объект. При использовании var in [integer list]посмотрите, может ли range()объект моделировать ту же последовательность. Очень близки по производительности к набору, но более лаконичны.
Мартин Питерс
37

Вы можете положить свои вещи в set. Набор поисков очень эффективен.

Пытаться:

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

edit В комментарии вы говорите, что хотите получить индекс элемента. К сожалению, наборы не имеют понятия положения элемента. Альтернативой является предварительная сортировка списка, а затем использование бинарного поиска каждый раз, когда вам нужно найти элемент.

NPE
источник
И если после этого я захочу узнать индекс этого значения, возможно ли и у вас есть быстрый способ сделать это?
Жан-Франсуа Галант
@ 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'] вернуть 'a' в ax = listValue () print ( x)
Тензин
12
Это правильный ответ Python, но это не очень хороший читаемый код.
Рик Хендерсон
1
Осторожно! Это соответствует, в то время как это очень вероятно, что вы не ожидали:o='--skip'; o in ("--skip-ias"); # returns True !
Alex F
3
@ Алекс F inоператор работает так же, чтобы проверить членство в подстроке. Запутанная часть здесь, вероятно, ("hello")состоит в том, что это не кортеж с одним значением, а ("hello",)запятая имеет значение. o in ("--skip-ias",)это , Falseкак ожидалось.
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"

Это будет хорошей идеей, только если a не изменится, и, таким образом, мы можем выполнить часть dict () один раз, а затем использовать ее повторно. Если что-то изменилось, пожалуйста, предоставьте более подробную информацию о том, что вы делаете.

Уинстон Эверт
источник
Это работает, но не тогда, когда реализовано в моем коде: «Ошибка типа: неустранимый тип:« список »
Жан-Франсуа Галлан,
1
@ Jean-FrancoisGallant, это, вероятно, потому что вы используете списки, где вам действительно следует использовать кортежи. Если вы хотите получить исчерпывающий совет о том, как ускорить ваш код, вы должны опубликовать его на codereview.stackexchange.com. Там вы получите советы по стилю и производительности.
Уинстон Эверт
1
Это очень умное решение проблемы. Вместо try, кроме конструкции, я бы сделал: a_index = index.get (7), который по умолчанию будет None, если ключ не найден.
murphsp1
15

Первоначальный вопрос был:

Какой самый быстрый способ узнать, существует ли значение в списке (список с миллионами значений в нем) и каков его индекс?

Таким образом, есть две вещи, которые нужно найти:

  1. это элемент в списке, и
  2. что такое индекс (если в списке).

Для этого я изменил код @xslittlegrass для вычисления индексов во всех случаях и добавил дополнительный метод.

Результаты

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

Методы:

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

Результаты показывают, что метод 5 является самым быстрым.

Интересно, что методы try и set эквивалентны по времени.


Тестовый код

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
источник
Опечатка в вашем описании («обратный цикл» должен быть «обратный поиск», нет?)
Cam U
@ CamU - да, поправили. Спасибо, что заметили.
DarrylG
7

Похоже, что ваше приложение может получить преимущество от использования структуры данных Bloom Filter.

Короче говоря, просмотр фильтра Блума может очень быстро сказать вам, если значение ОПРЕДЕЛЕННО НЕ присутствует в наборе. В противном случае вы можете выполнить поиск медленнее, чтобы получить индекс значения, которое МОЖЕТ БЫТЬ в списке. Поэтому, если ваше приложение имеет тенденцию получать результат «не найден» гораздо чаще, чем результат «найден», вы можете ускорить процесс, добавив фильтр Блума.

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

matt2000
источник
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оператор высоко оптимизирован и, разумеется, именно то, что вы хотите (с помощью listилиset ).

Chris_Rands
источник
NAN == NAN, возвращающий false, не имеет в этом ничего необычного. Это поведение, определенное в стандарте IEEE 754.
TommyD
2

Или используйте __contains__:

sequence.__contains__(value)

Демо-версия:

>>> l=[1,2,3]
>>> l.__contains__(3)
True
>>> 
U10-Forward
источник
2

Решение @Winston Ewert дает большое ускорение для очень больших списков, но этот ответ на стекопоток указывает, что конструкция try: / exception: / else: будет замедлена, если часто достигается ветвь exc. Альтернативой является использование .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.

user3897315
источник
1

Это не код, а алгоритм очень быстрого поиска.

Если ваш список и значение, которое вы ищете, все числа, это довольно просто. Если строки: посмотрите внизу:

  • -Позвольте "n" быть длиной вашего списка
  • -Дополнительный шаг: если вам нужен индекс элемента: добавьте второй список в список с текущим индексом элементов (от 0 до n-1) - см. Позже
  • Заказать свой список или его копию (.sort ())
  • Переберите:
    • Сравните ваш номер с п / 2-м элементом списка
      • Если больше, повторите цикл между индексами n / 2-n
      • Если меньше, повторите цикл между индексами 0-n / 2
      • Если то же самое: вы нашли это
  • Продолжайте сужать список, пока не найдете его или не получите только 2 числа (ниже и выше того, которое вы ищете)
  • Это найдет любой элемент не более чем в 19 шагах для списка 1.000.000 (log (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.

Это возвращает индексы в хорошем списке.

Есть и другие способы проверить эту проблему - однако понимание списка достаточно быстро, добавляя факт написания достаточно быстро, чтобы решить проблему.

Vaidøtas Ivøška
источник
-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
Tabin1000
источник
-2

Код для проверки наличия двух элементов в массиве, произведение которых равно k:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
Рави Танвар
источник