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.