La forma más rápida de comprobar si existe un valor en una lista

Resuelto Jean-Francois Gallant asked hace 13 años • 12 respuestas

¿Cuál es la forma más rápida de comprobar si un valor existe en una lista muy grande (con millones de valores) y cuál es su índice?

Jean-Francois Gallant avatar Sep 27 '11 22:09 Jean-Francois Gallant
Aceptado
7 in a

La forma más clara y rápida de hacerlo.

También puedes considerar usar un set, pero construir ese conjunto a partir de tu lista puede llevar más tiempo del que ahorrará una prueba de membresía más rápida. La única forma de estar seguro es realizar una buena evaluación comparativa. (esto también depende de las operaciones que requiera)

Rafe Kettler avatar Sep 27 '2011 15:09 Rafe Kettler

Como han dicho otros, inpuede ser muy lento para listas grandes. A continuación se muestran algunas comparaciones de las actuaciones de in, sety bisect. Tenga en cuenta que el tiempo (en segundos) está en escala logarítmica.

ingrese la descripción de la imagen aquí

Código para probar:

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 = []

    # adjust range down if runtime is too long or up if there are too many zero entries in any of the time_method lists
    Nls = [x for x in range(10000, 30000, 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(method_in(a, b, c))
        time_method_set_in.append(method_set_in(a, b, c))
        time_method_bisect.append(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.yscale('log')
    plt.show()


profile()
xslittlegrass avatar Dec 04 '2016 20:12 xslittlegrass

Podrías poner tus artículos en un archivo set. Las búsquedas de conjuntos son muy eficientes.

Intentar:

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

editar En un comentario dices que te gustaría obtener el índice del elemento. Desafortunadamente, los conjuntos no tienen noción de la posición de los elementos. Una alternativa es ordenar previamente la lista y luego utilizar la búsqueda binaria cada vez que necesite encontrar un elemento.

NPE avatar Sep 27 '2011 15:09 NPE

La pregunta original era:

¿Cuál es la forma más rápida de saber si un valor existe en una lista (una lista con millones de valores) y cuál es su índice?

Por tanto, hay dos cosas que encontrar:

  1. es un elemento en la lista, y
  2. ¿Cuál es el índice (si está en la lista)?

Para lograr esto, modifiqué el código @xslittlegrass para calcular índices en todos los casos y agregué un método adicional.

Resultados

ingrese la descripción de la imagen aquí

Los métodos son:

  1. en - básicamente si x en b: devuelve b.index(x)
  2. try--try/catch en b.index(x) (evita tener que comprobar si x está en b)
  3. set--básicamente si x en set(b): devuelve b.index(x)
  4. bisect: ordenar b con su índice, búsqueda binaria de x en ordenado (b). Nota mod de @xslittlegrass que devuelve el índice en la b ordenada, en lugar de la b original)
  5. inversa: forma un diccionario de búsqueda inversa d para b; entonces d[x] proporciona el índice de x.

Los resultados muestran que el método 5 es el más rápido.

Curiosamente, los métodos try y set son equivalentes en el tiempo.


Código de prueba

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 avatar Nov 13 '2019 19:11 DarrylG