Saltar al contenido

¿Puede Python probar la pertenencia de varios valores en una lista?

Solución:

Esto hace lo que quiere y funcionará en casi todos los casos:

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True

La expresion 'a','b' in ['b', 'a', 'foo', 'bar'] no funciona como se esperaba porque Python lo interpreta como una tupla:

>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)

Otras opciones

Hay otras formas de ejecutar esta prueba, pero no funcionarán para tantos tipos diferentes de entradas. Como señala Kabie, puede resolver este problema usando conjuntos …

>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True

…algunas veces:

>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Los conjuntos solo se pueden crear con elementos hash. Pero la expresión generadora all(x in container for x in items) puede manejar casi cualquier tipo de contenedor. El único requisito es que container ser repetible (es decir, no ser un generador). items puede ser iterable en absoluto.

>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True

Pruebas de velocidad

En muchos casos, la prueba de subconjunto será más rápida que all, pero la diferencia no es sorprendente, excepto cuando la pregunta es irrelevante porque los conjuntos no son una opción. Convertir listas en conjuntos solo con el propósito de una prueba como esta no siempre valdrá la pena. Y convertir generadores en conjuntos a veces puede ser un desperdicio increíble, lo que ralentiza los programas en muchos órdenes de magnitud.

Aquí hay algunos puntos de referencia a modo de ilustración. La mayor diferencia se produce cuando ambos container y items son relativamente pequeños. En ese caso, el enfoque de subconjunto es aproximadamente un orden de magnitud más rápido:

>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Esto parece una gran diferencia. Pero mientras container es un conjunto, all sigue siendo perfectamente utilizable a escalas mucho mayores:

>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

El uso de pruebas de subconjuntos es aún más rápido, pero solo alrededor de 5 veces en esta escala. El aumento de velocidad se debe a la rapidez de Python c-implementación respaldada de set, pero el algoritmo fundamental es el mismo en ambos casos.

Si tu items ya están almacenados en una lista por otras razones, entonces tendrá que convertirlos en un conjunto antes de usar el enfoque de prueba de subconjuntos. Luego, la aceleración se reduce a aproximadamente 2.5x:

>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Y si tu container es una secuencia y debe convertirse primero, luego la aceleración es aún menor:

>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

La única vez que obtenemos resultados desastrosamente lentos es cuando nos vamos container como una secuencia:

>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Y, por supuesto, solo lo haremos si es necesario. Si todos los elementos en bigseq son hash, entonces haremos esto en su lugar:

>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Eso es solo 1,66 veces más rápido que la alternativa (set(bigseq) >= set(bigsubseq), cronometrado arriba en 4.36).

Por lo tanto, las pruebas de subconjuntos son generalmente más rápidas, pero no por un margen increíble. Por otro lado, veamos cuándo all es más rápido. Y si items tiene diez millones de valores y es probable que tenga valores que no están en container?

>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

En este caso, convertir el generador en un conjunto resulta un desperdicio increíble. los set El constructor tiene que consumir todo el generador. Pero el comportamiento de cortocircuito de all asegura que solo se necesita consumir una pequeña porción del generador, por lo que es más rápido que una prueba de subconjunto por cuatro órdenes de magnitud.

Este es un ejemplo extremo, sin duda. Pero como muestra, no se puede asumir que un enfoque u otro será más rápido en todos los casos.

El resultado

La mayor parte del tiempo, convirtiendo container a un conjunto vale la pena, al menos si todos sus elementos son hash. Eso es porque in para conjuntos es O (1), mientras que in para secuencias es O (n).

Por otro lado, el uso de pruebas de subconjuntos probablemente solo valga la pena a veces. Definitivamente hágalo si sus elementos de prueba ya están almacenados en un conjunto. De lo contrario, all es solo un poco más lento y no requiere almacenamiento adicional. También se puede utilizar con grandes generadores de elementos y, en ocasiones, proporciona una aceleración masiva en ese caso.

Otra forma de hacerlo:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

Estoy bastante seguro in tiene mayor precedencia que , por lo que su declaración se interpreta como 'a', ('b' in ['b' ...]), que luego se evalúa como 'a', True ya que 'b' está en la matriz.

Consulte la respuesta anterior para saber cómo hacer lo que quiere.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags : /

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *