Generar una lista de números y sus contrapartes negativas en Python

32

¿Hay una línea conveniente para generar una lista de números y sus contrapartes negativas en Python?

Por ejemplo, digamos que quiero generar una lista con los números 6 a 9 y -6 a -9.

Mi enfoque actual es:

l = [x for x in range(6,10)]
l += [-x for x in l]

Un simple "one-liner" 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 inconveniente.

upe
fuente
3
¿Deberían los números positivos venir antes que los negativos?
Erich
2
@Erich No, el orden no importa en mi caso.
Upe
66
Si el orden no importa, ¿es necesario que sea una lista? ¿Estaría bien un conjunto (que no está ordenado) o un generador o tupla (ambos ordenados)?
JG
@JG Quizás quiera crear una figura más adelante. Supuestamente usando dispersión, etc. Entonces cualquiera scalar or array-like, shapeestaría bien.
Upe
2
La mayoría de estas respuestas se leen como algunas soluciones anti-golf; ordenación, funciones generadoras, itertools. Prefiero mantener el código que proporcionó que la mayoría de las 'respuestas'.
Peilonrayz

Respuestas:

47

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]
Datanovice
fuente
53

Crea una función agradable y legible:

def range_with_negatives(start, end):
    for x in range(start, end):
        yield x
        yield -x

Uso:

list(range_with_negatives(6, 10))

Así es como obtienes una línea conveniente para cualquier cosa. Evita tratar de parecer un hacker profesional mágico.

Derte Trdelnik
fuente
21
El prejuicio contra las comprensiones de listas / dictados es bastante común en SO, y generalmente se justifica diciendo que no son "legibles". La legibilidad inherente de una construcción (en lugar de cómo se usa) es en gran medida subjetiva. Personalmente, creo que las comprensiones son más legibles, y esta solución es mucho menos (comparar: "escriba la presión arterial de cada hombre mayor de 50 años" frente a "Ir a través de cada persona. OK, ¿son hombres? Si es así, entonces ..." ) Los uso por esta razón, no porque estoy tratando de "parecer" algo. Las comprensiones largas pueden dividirse en varias líneas si ese es el problema.
jez
2
Lo suficientemente justo. Pero ahí es donde entra en juego la subjetividad: Yo personalmente hago encontrar la solución lista de los al menos tan legible, en términos de la tensión mental, ya que la función de generador. Sin embargo, mejoraría la legibilidad (o más bien, legibilidad para mí) de la respuesta de Datanovice al cambiar el nombre ya signedValue. Para mí, el valor agregado real del enfoque de definir una función sería entregar resultados en el orden estricto que la pregunta formulada (positiva, luego negativa) mientras se evita el problema de la línea única de Barmar chainen la que los argumentos numéricos ligeramente diferentes tienen ser codificado en dos ocasiones.
jez
2
Estoy de acuerdo con el sentimiento general sobre las comprensiones de listas, pero definir una función como esta también es una técnica muy útil para conocer. (Es especialmente poderoso recopilar resultados de un algoritmo recursivo escribiendo un generador recursivo y pasando el resultado de nivel superior a listlo que sea). Después de años de tratar de codificar la mejor manera de tomar estas decisiones, el único principio general que tiene sentido para mí : si hay un buen nombre obvio para dar a algo en el programa, aproveche la oportunidad para hacerlo (y las funciones son una de las formas en que lo hacemos).
Karl Knechtel
44

Yo diría que la solución más simple es descomprimir dos rangos en una lista usando el *operador de desempaquetado:

>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]

No solo es la respuesta más corta propuesta hasta ahora, sino que también es la más eficaz, ya que solo construye una lista única y no involucra llamadas de función más allá de las dos ranges.

Verifiqué esto probando todas las respuestas de esta pregunta usando el timeitmódulo:

Respuesta ID Método timeit result
-------------------------------------------------- ------------------------------------------------
(en cuestión) [x para x en el rango (6,10)] + [y para y en el rango (-9, -5)] 0.843 usec por ciclo
(esta respuesta) [* rango (6, 10), * rango (-9, -5)] 0.509 usec por ciclo
61348876 [y para x en el rango (6,10) para y en (x, -x)] 0.754 usec por ciclo
61349149 list (range_with_negatives (6, 10)) 0.795 usec por ciclo
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), rango (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

(Pruebas de timeit realizadas con Python 3.6.9 en mi propia computadora (especificaciones promedio))

RoadrunnerWMC
fuente
2
No estoy demasiado interesado en asumir que el rendimiento es relevante cuando todo lo que tiene es un ejemplo con <10 elementos, pero esta es claramente la solución más simple.
JollyJoker
¿Cómo calcula el inicio y el final de los valores negativos sin codificarlos?
aldokkani hace
2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
21

Puede usar itertools.chain()para concatenar los dos rangos.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
fuente
1
Usaría 'rango (-6, -10, -1)' para que la claridad del orden no importe (y reemplazaría el 6 y el 10 con variables)
Maarten Fabré
8

Puedes 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 podría ser más lento porque usa la multiplicación, pero debería ser fácil de leer.

Si desea una solución puramente funcional, también puede importar itertools.starmapy 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.

Frank Vel
fuente
66
Encuentro el uso de product, starmapy opertaor.mulinnecesariamente obtuso en comparación con las comprensiones de listas anidadas, pero apruebo la sugerencia de usar la multiplicación. [x * sign for sign in (1, -1) for x in range(6, 10)]es solo un 10% más lento que [y for x in range(6, 10) for y in (x, -x)], y en casos donde el orden importa, más de 3 veces más rápido que ordenar el enfoque basado en tuplas.
ApproachingDarknessFish
Esta es una buena idea, pero quizás demasiado específica para los detalles del problema. Las técnicas ofrecidas por otros se aplican de manera más general.
Karl Knechtel
@ApproachingDarknessFish Estoy de acuerdo starmapy muldebería evitarse, pero creo productque lo hace más legible, ya que agrupa los iteradores y sus elementos por separado. Los bucles dobles en la comprensión de la lista también pueden ser confusos, debido a su orden inesperado.
Frank Vel
5

Estás muy cerca, combinando dos rangeobjetos. 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 rangeobjeto 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 hacer un "súper iterador". Luego pase eso list()y obtendrá una lista concreta, con todos los números que desee en la memoria.

Greg Ward
fuente
44
Esta respuesta es valiosa simplemente por la comprensión que [x for x in ...]se escribe mejor list(...).
Karl Knechtel
3

Pesando con otra posibilidad más.

Si desea legibilidad, su línea 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)]
KyleL
fuente
3

En mi opinión, el enfoque que se itertools.chainpresenta en un par de otras respuestas es definitivamente el más claro 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 rangeobjetos explícitos y, por lo tanto, evitar hacer todos los cálculos matemáticos necesarios para la rangeindexación negativa , 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]

Un poco detallado, pero lo suficientemente legible.

Otra opción similar es utilizar el desempaquetado de tuplas / argumentos con plain chain:

>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]

Más conciso, pero creo que desempacar las tuplas es más difícil de asimilar en un escaneo rápido.

hBy2Py
fuente
3

Esta es una variación de un tema (ver @Derte trdelník 's respuesta ) siguiendo la filosofía de itertoolsdonde

Los bloques de construcción iteradores [...] son ​​útiles por sí mismos o en combinación.

La idea es que, mientras definimos 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 rangeiterador particular :

list(interleaved_negatives(range(6, 10)))
PiCTo
fuente
1

Si desea mantener el orden que ha especificado, puede utilizar el generador de rango incorporado 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 value

Lo 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.

Jeff
fuente
2
Esto es muy ineficiente para grandes valores de start.
Solomon Ucko
1

Puede haber diferentes formas de hacer el trabajo.

Variables dadas: 1. inicio = 6 2. parada = 10

Puede probar esto también, para un enfoque diferente:

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)
hp_elite
fuente
-1

Me gustan las simetrías.

a = 6 b = 10

nums = [x + y para x en (- (a + b-1), 0) para y en rango (a, b)]

El resultado debe ser -9, -8, ..., 8, 9.

Creo que la expresión nums se puede mejorar, lo que sigue "en" y "rango" todavía no me parece equilibrado.

CSQL
fuente