¿Existe una sola línea conveniente para generar una lista de números y sus contrapartes negativas en Python?
Por ejemplo, supongamos que quiero generar una lista con los números del 6 al 9 y del -6 al -9.
Mi enfoque actual es:
l = [x for x in range(6,10)] l += [-x for x in l]Una simple "una sola línea" sería:
l = [x for x in range(6,10)] + [y for y in range(-9, -5)]Sin embargo, generar dos listas y luego unirlas parece un inconveniente.
Puede haber diferentes maneras de hacer el trabajo.
Variables dadas: 1. inicio=6 2. parada=10
def mirror_numbers(start,stop): if start<stop: val=range(start,stop) return [j if i < len(val) else -j for i,j in enumerate([x for x in val]*2) ] mirror_numbers(6,10)Simplemente me gustan las simetrías.
a = 6 b = 10 nums = [x+y for x in (-(a+b-1),0) for y in range(a,b)] El resultado es [-9, -8, -7, -6, 6, 7, 8, 9] .
Creo que la expresión nums es mejorable, lo que sigue "in" y "range" me sigue pareciendo desequilibrado.
Parece que solo dos respuestas realmente dieron frases ingeniosas, así que aquí hay otra:
[i for l in map(lambda x: (x, -x), range(6, 10)) for i in l] [6, -6, 7, -7, 8, -8, 9, -9]No estoy seguro de si el orden es importante, pero podría crear una tupla y descomprimirla en una lista de comprensión.
nums = [y for x in range(6,10) for y in (x,-x)] print(nums) [6, -6, 7, -7, 8, -8, 9, -9]Puede usar itertools.chain() para concatenar los dos rangos.
import itertools list(itertools.chain(range(6, 10), range(-9, -5)))Crea una función agradable y legible:
def range_with_negatives(start, end): for x in range(start, end): yield x yield -xUso:
list(range_with_negatives(6, 10))Así es como obtienes una sola línea conveniente para cualquier cosa. Evita tratar de parecer un hacker profesional mágico.
Puede usar itertools.product , que es el producto cartesiano.
[sign*x for sign, x in product((-1, 1), range(6, 10))] [-6, -7, -8, -9, 6, 7, 8, 9]Esto puede ser más lento porque usas la multiplicación, pero debería ser fácil de leer.
Si desea una solución puramente funcional, también puede importar itertools.starmap y operator.mul :
from itertools import product, starmap from operator import mul list(starmap(mul, product((-1, 1), range(6, 10))))Sin embargo, esto es menos legible.
Si desea mantener el orden que ha especificado, puede utilizar el generador de rango integrado de Python con un condicional:
def range_with_negs(start, stop): for value in range(-(stop-1), stop): if (value <= -start) or (value >= start): yield valueLo que te da la salida:
In [1]: list(range_with_negs(6, 10)) Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]Y también funciona con 0 como inicio para el rango completo.
Estás muy cerca, con la combinación de dos objetos de range . Pero hay una manera más fácil de hacerlo:
>>> list(range(6, 10)) + list(range(-9, -5)) [6, 7, 8, 9, -9, -8, -7, -6] Es decir, convierta cada objeto de range en una lista y luego concatene las dos listas.
Otro enfoque, usando itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5))) [6, 7, 8, 9, -9, -8, -7, -6] itertools.chain() es como un + generalizado: en lugar de agregar dos listas, encadena un iterador tras otro para crear un "super-iterador". Luego páselo a list() y obtendrá una lista concreta, con todos los números que desea en la memoria.
Diría que la solución más simple es desempaquetar dos rangos en una lista usando el operador de desempaquetado * :
>>> [*range(6, 10), *range(-9, -5)] [6, 7, 8, 9, -9, -8, -7, -6] Esta no solo es la respuesta más corta propuesta hasta ahora, sino que también es la de mayor rendimiento, porque solo construye una sola lista y no implica llamadas a funciones más allá de los dos range .
Verifiqué esto probando todas las respuestas de esta pregunta usando el módulo timeit :
ID de respuesta Método timeit resultado -------------------------------------------------- ------------------------------------------------ (en cuestión) [x for x in range(6,10)] + [y for y in range(-9, -5)] 0.843 usec por bucle (esta respuesta) [*rango(6, 10), *rango(-9, -5)] 0.509 usec por ciclo 61348876 [y para x en rango (6,10) para y en (x,-x)] 0,754 usec por ciclo 61349149 list(range_with_negatives(6, 10)) 0,795 usec por bucle 61348914 list(itertools.chain(range(6, 10), range(-9, -5))) 0,709 usec por ciclo 61366995 [signo*x para signo, x en itertools.product((-1, 1), range(6, 10))] 0,899 usec por ciclo 61371302 lista(rango(6, 10)) + lista(rango(-9, -5)) 0,729 usec por ciclo 61367180 list(range_with_negs(6, 10)) 1,95 usec por ciclo
(prueba de timeit realizada con Python 3.6.9 en mi propia computadora (especificaciones promedio))
Sopesando otra posibilidad.
Si desea legibilidad, su frase original era bastante buena, pero cambiaría los rangos para que sean los mismos, ya que creo que los límites negativos hacen que las cosas sean menos claras.
[x for x in range(6, 10)] + [-x for x in range(6, 10)]En mi opinión, el enfoque que usa itertools.chain presentado en un par de otras respuestas es definitivamente el más limpio de los proporcionados hasta ahora.
Sin embargo, dado que en su caso el orden de los valores no importa , puede evitar tener que definir dos objetos de range explícitos y, por lo tanto, evitar hacer todas las operaciones matemáticas necesarias para la indexación de range negativo, utilizando itertools.chain.from_iterable :
>>> import itertools >>> list(itertools.chain.from_iterable((x, -x) for x in range(6, 10))) [6, -6, 7, -7, 8, -8, 9, -9]Tad detallado, pero lo suficientemente legible.
Otra opción similar es usar el desempaquetado de tupla/argumento con chain simple:
>>> list(itertools.chain(*((x, -x) for x in range(6, 10)))) [6, -6, 7, -7, 8, -8, 9, -9]Más conciso, pero encuentro tuple desempaquetar más difícil de asimilar en un escaneo rápido.
Esta es una variación de un tema (ver la respuesta de @Derte Trdelnik ) siguiendo la filosofía de itertools donde
Los bloques de construcción del iterador [...] son útiles solos o en combinación.
La idea es que, mientras estamos definiendo una nueva función, también podríamos hacerla genérica:
def interleaved_negatives(it): for i in it: yield i yield -i y aplicarlo a un iterador de range particular:
list(interleaved_negatives(range(6, 10)))usando itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5))) [6, 7, 8, 9, -9, -8, -7, -6] itertools.chain() es como un + generalizado: en lugar de agregar dos listas, encadena un iterador tras otro para crear un "super-iterador". Luego páselo a list() y obtendrá una lista concreta, con todos los números que desea en la memoria.