Gere uma lista de números e suas contrapartes negativas em Python

32

Existe um one-liner conveniente para gerar uma lista de números e suas contrapartes negativas no Python?

Por exemplo, digamos que eu queira gerar uma lista com os números 6 a 9 e -6 a -9.

Minha abordagem atual é:

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

Um simples "one-liner" seria:

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

No entanto, gerar duas listas e juntá-las parece inconveniente.

upe
fonte
3
Os números positivos devem vir antes dos negativos?
Erich
2
@ Erich Não, o pedido não importa no meu caso.
upe 21/04
6
Se o pedido não importa, ele precisa ser uma lista? Um conjunto estaria OK (que não está ordenado) ou um gerador ou tupla (ambos pedidos)?
JG
@JG Eu posso querer criar uma figura mais tarde. Supostamente usando scatter etc. Portanto, qualquer um scalar or array-like, shapeseria bom.
upe 22/04
2
A maioria dessas respostas está parecida com algumas soluções anti-golfe; triagem, funções de gerador, ferramentas de controle. Prefiro manter o código que você forneceu do que a maioria das 'respostas'.
Peilonrayz 22/04

Respostas:

46

Não tenho certeza se o pedido é importante, mas você pode criar uma tupla e descompactá-la em uma lista de compreensão.

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
Datanovice
fonte
53

Crie uma função agradável e legível:

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

Uso:

list(range_with_negatives(6, 10))

É assim que você obtém uma linha conveniente para qualquer coisa. Evite tentar parecer um hacker profissional mágico.

Derte Trdelnik
fonte
21
O preconceito contra as compreensões de lista / dict é bastante comum no SO, e geralmente é justificado dizendo que eles não são "legíveis". A legibilidade inerente de uma construção (em vez de como é usada) é amplamente subjetiva. Pessoalmente, acho as compreensões mais legíveis, e essa solução muito menos (compare: "anote a pressão sanguínea de todo homem acima de 50 anos" vs. ) Eu os uso por esse motivo, não porque estou tentando "parecer" qualquer coisa. Compreensões longas podem ser interrompidas em várias linhas, se esse for o problema.
jez 22/04
2
Justo. Mas é aí que entra a subjetividade: eu, pessoalmente , acho a solução comp de lista pelo menos tão legível, em termos de tensão mental, quanto a função geradora. No entanto, eu melhoraria a legibilidade (ou melhor, legibilidade para mim) da resposta de Datanovice renomeando ypara signedValue. Para mim, o verdadeiro valor agregado da abordagem de definir uma função seria fornecer resultados na ordem estrita que a pergunta solicitada (positiva, depois negativa), evitando o problema da linha chainúnica de Barmar, na qual argumentos numéricos ligeiramente diferentes para ser codificado duas vezes.
jez 22/04
2
Concordo com o sentimento geral sobre a compreensão de listas, mas definir uma função como essa também é uma técnica muito útil de se conhecer. (É especialmente poderoso coletar resultados de um algoritmo recursivo escrevendo um gerador recursivo e passando o resultado de nível superior para o que listfor.) Depois de anos tentando codificar a melhor maneira de tomar essas decisões, o único princípio geral que faz sentido para mim : se houver um nome óbvio e bom para dar a algo no programa, aproveite a oportunidade para fazê-lo (e as funções são uma das maneiras de fazer isso).
Karl Knechtel
44

Eu diria que a solução mais simples é descompactar dois intervalos em uma lista usando o *operador de descompactação:

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

Além de ser a resposta mais curta proposta até agora, também é a que apresenta melhor desempenho, porque constrói apenas uma lista única e não envolve chamadas de função além dos dois rangesegundos.

Eu verifiquei isso testando todas as respostas desta pergunta usando o timeitmódulo:

Identificação da resposta Método timeit result
-------------------------------------------------- ------------------------------------------------
(em questão) [x para x no intervalo (6,10)] + [y para y no intervalo (-9, -5)] 0,843 usec por loop
(esta resposta) [* intervalo (6, 10), * intervalo (-9, -5)] 0,509 usec por loop
61348876 [y para x no intervalo (6,10) para y em (x, -x)] 0,754 usec por loop
61349149 list (range_with_negatives (6, 10)) 0.795 usec por loop
61348914 list (itertools.chain (intervalo (6, 10), intervalo (-9, -5))) 0,709 usec por loop
61366995 [sinal * x para sinal, x em itertools.product ((- 1, 1), intervalo (6, 10))]] 0,899 usec por loop
61371302 list (intervalo (6, 10)) + list (intervalo (-9, -5)) 0,729 usec por loop
61367180 list (range_with_negs (6, 10)) 1,95 usec por loop

(timeit testado com o Python 3.6.9 no meu próprio computador (especificações médias))

RoadrunnerWMC
fonte
2
Não estou muito interessado em assumir que o desempenho é relevante quando tudo o que você tem é um exemplo com <10 itens, mas essa é claramente a solução mais simples.
JollyJoker 24/04
Como você descobre o início e o fim dos valores negativos sem codificá-los?
aldokkani
2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
21

Você pode usar itertools.chain()para concatenar os dois intervalos.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
fonte
11
Eu usaria 'range (-6, -10, -1)' para que a clareza da ordem não seja importante (e substitua os 6 e 10 pelas variáveis)
Maarten Fabré
8

Você pode usar itertools.product, que é o produto cartesiano.

[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]

Isso pode ser mais lento porque você usa a multiplicação, mas deve ser fácil de ler.

Se você deseja uma solução puramente funcional, também pode importar itertools.starmape operator.mul:

from itertools import product, starmap
from operator import mul

list(starmap(mul, product((-1, 1), range(6, 10))))

No entanto, isso é menos legível.

Frank Vel
fonte
6
Acho o uso de product, starmape opertaor.muldesnecessariamente obtuso em comparação com as compreensões de listas aninhadas, mas aprovo a sugestão de usar a multiplicação. [x * sign for sign in (1, -1) for x in range(6, 10)]é apenas 10% mais lento que [y for x in range(6, 10) for y in (x, -x)]e, nos casos em que a ordem é importante, mais de 3x mais rápido do que classificar a abordagem baseada em tupla.
ApproachingDarknessFish
Essa é uma idéia interessante, mas talvez um pouco específica demais para os detalhes do problema. As técnicas oferecidas por outros se aplicam de maneira mais geral.
Karl Knechtel
@ApproachingDarknessFish Concordo que starmape muldeve ser evitado, mas eu acho que producttorna mais legível, uma vez que agrupa os iteradores e seus elementos separadamente. Loops duplos na compreensão da lista também podem ser confusos, devido à sua ordem inesperada.
Frank Vel
5

Você está realmente perto, combinando dois rangeobjetos. Mas há uma maneira mais fácil de fazer isso:

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

Ou seja, converta cada rangeobjeto em uma lista e concatene as duas listas.

Outra abordagem, usando itertools:

>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]

itertools.chain()é como um generalizado +: em vez de adicionar duas listas, ele liga um iterador após outro para criar um "super iterador". Depois passe isso para list()e você obterá uma lista concreta, com todos os números que deseja na memória.

Greg Ward
fonte
4
Essa resposta é valiosa simplesmente para o insight que [x for x in ...]é melhor escrito list(...).
Karl Knechtel
3

Pesando com mais uma possibilidade.

Se você deseja legibilidade, sua linha única original foi muito boa, mas eu alteraria os intervalos para os mesmos, pois acho que os limites negativos tornam as coisas menos claras.

[x for x in range(6, 10)] + [-x for x in range(6, 10)]
KyleL
fonte
3

A OMI, a abordagem utilizada itertools.chainapresentada em algumas outras respostas é definitivamente a mais limpa dentre as fornecidas até agora.

No entanto, como no seu caso a ordem dos valores não importa , você pode evitar a definição de dois rangeobjetos explícitos e, assim, evitar toda a matemática isolada necessária para a rangeindexação negativa , usando 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]

Um pouco detalhado, mas legível o suficiente.

Outra opção semelhante é usar a tupla / argumento descompactar com plain chain:

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

Mais conciso, mas acho mais difícil descompactar as tuplas em uma verificação rápida.

hBy2Py
fonte
3

Esta é uma variação de um tema (ver @Derte trdelník 's resposta ) seguindo a filosofia de itertoolsonde

os blocos de construção do iterador [...] são úteis sozinhos ou combinados.

A idéia é que, enquanto definimos uma nova função, também podemos torná-la genérica:

def interleaved_negatives(it):
    for i in it:
        yield i
        yield -i

e aplique-o a um rangeiterador específico :

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

Se você deseja manter a ordem que especificou, pode usar o gerador de alcance interno do Python com uma condição:

def range_with_negs(start, stop):
    for value in range(-(stop-1), stop):      
        if (value <= -start) or (value >= start):
            yield value

O que fornece a saída:

In [1]: list(range_with_negs(6, 10))
Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]

E também funciona com 0 como o início de toda a gama.

Jeff
fonte
2
Isso é muito ineficiente para grandes valores de start.
Solomon Ucko
1

Pode haver diferentes maneiras de fazer o trabalho.

Variáveis ​​fornecidas: 1. start = 6 2. stop = 10

Você pode tentar isso também, para uma abordagem 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
fonte
-1

Eu gosto de simetrias.

a = 6 b = 10

nums = [x + y para x em (- (a + b-1), 0) para y no intervalo (a, b)]

O resultado deve ser -9, -8, ..., 8, 9.

Eu acredito que a expressão nums pode ser melhorada, o que se segue "in" e "range" ainda me parece desequilibrado.

CSQL
fonte