Generate list of numbers and their negative counterparts in Python
Question:
Is there a convenient one-liner to generate a list of numbers and their negative counterparts in Python?
For example, say I want to generate a list with the numbers 6 to 9 and -6 to -9.
My current approach is:
l = [x for x in range(6,10)]
l += [-x for x in l]
A simple “one-liner” would be:
l = [x for x in range(6,10)] + [y for y in range(-9, -5)]
However, generating two lists and then joining them together seems inconvenient.
Answers:
I am unsure if order matters, but you could create a tuple and unpack it in a list comprehension.
nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
You can use itertools.chain()
to concatenate the two ranges.
import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Create a nice and readable function:
def range_with_negatives(start, end):
for x in range(start, end):
yield x
yield -x
Usage:
list(range_with_negatives(6, 10))
That is how you get a convenient one-liner for anything. Avoid trying to look like a magic pro hacker.
You can use itertools.product
, which is the cartesian product.
[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]
This might be slower because you use multiplication, but should be easy to read.
If you want a purely functional solution, you can also import itertools.starmap
and operator.mul
:
from itertools import product, starmap
from operator import mul
list(starmap(mul, product((-1, 1), range(6, 10))))
However, this is less readable.
If you want to keep the order you’ve specified, you can make use of Python’s built-in range generator with a conditional:
def range_with_negs(start, stop):
for value in range(-(stop-1), stop):
if (value <= -start) or (value >= start):
yield value
Which gives you the output:
In [1]: list(range_with_negs(6, 10))
Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]
And also works with 0 as the start for the full range.
You’re really close, with combining two range
objects. But there is an easier way to do it:
>>> list(range(6, 10)) + list(range(-9, -5))
[6, 7, 8, 9, -9, -8, -7, -6]
That is, convert each range
object to a list, and then concatenate the two lists.
Another approach, using itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]
itertools.chain()
is like a generalized +
: instead of adding two lists, it chains one iterator after another to make a “super-iterator”. Then pass that to list()
and you get a concrete list, with all the numbers you want in memory.
I’d say the simplest solution is to unpack two ranges into a list using the *
unpacking operator:
>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]
Not only is this the shortest answer proposed yet, it’s also the most performant, because it only constructs a single list and involves no function calls beyond the two range
s.
I verified this by testing all of this question’s answers using the timeit
module:
Answer ID Method timeit result
--------------------------------------------------------------------------------------------------
(in question) [x for x in range(6,10)] + [y for y in range(-9, -5)] 0.843 usec per loop
(this answer) [*range(6, 10), *range(-9, -5)] 0.509 usec per loop
61348876 [y for x in range(6,10) for y in (x,-x)] 0.754 usec per loop
61349149 list(range_with_negatives(6, 10)) 0.795 usec per loop
61348914 list(itertools.chain(range(6, 10), range(-9, -5))) 0.709 usec per loop
61366995 [sign*x for sign, x in itertools.product((-1, 1), range(6, 10))] 0.899 usec per loop
61371302 list(range(6, 10)) + list(range(-9, -5)) 0.729 usec per loop
61367180 list(range_with_negs(6, 10)) 1.95 usec per loop
(timeit testing performed with Python 3.6.9 on my own computer (average specs))
Weighing in with yet another possibility.
If you want readability your original one-liner was pretty good, but I would change the ranges to be the same as I think the negative bounds make things less clear.
[x for x in range(6, 10)] + [-x for x in range(6, 10)]
IMO the approach using itertools.chain
presented in a couple of other answers is definitely the cleanest out of those provided so far.
However, since in your case the order of the values doesn’t matter, you can avoid having to define two explicit range
objects, and thus avoid doing all the off-by-one math necessary for negative range
indexing, by using 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 verbose, but readable enough.
Another similar option is to use tuple/argument unpacking with plain chain
:
>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]
More concise, but I find tuple unpacking harder to grok in a quick scan.
This is a variation on a theme (see @Derte Trdelnik‘s answer) following the philosophy of itertools
where
iterator building blocks […] are useful by themselves or in combination.
The idea is that, while we’re defining a new function, we might as well make it generic:
def interleaved_negatives(it):
for i in it:
yield i
yield -i
and apply it to a particular range
iterator:
list(interleaved_negatives(range(6, 10)))
There can be different ways to get the job done.
Variables given:
1. start=6
2. stop=10
You can try this also, for different approach:
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)
l just like symmetries.
a = 6
b = 10
nums = [x+y for x in (-(a+b-1),0) for y in range(a,b)]
The result is [-9, -8, -7, -6, 6, 7, 8, 9]
.
I believe that the nums expression can be improved, what follows “in” and “range” still looks unbalanced to me.
using itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]
itertools.chain()
is like a generalized +
: instead of adding two lists, it chains one iterator after another to make a “super-iterator”. Then pass that to list()
and you get a concrete list, with all the numbers you want in memory.
Seems like only two answers really gave one-liners, so here’s another one:
[i for l in map(lambda x: (x, -x), range(6, 10)) for i in l]
[6, -6, 7, -7, 8, -8, 9, -9]
Is there a convenient one-liner to generate a list of numbers and their negative counterparts in Python?
For example, say I want to generate a list with the numbers 6 to 9 and -6 to -9.
My current approach is:
l = [x for x in range(6,10)]
l += [-x for x in l]
A simple “one-liner” would be:
l = [x for x in range(6,10)] + [y for y in range(-9, -5)]
However, generating two lists and then joining them together seems inconvenient.
I am unsure if order matters, but you could create a tuple and unpack it in a list comprehension.
nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
You can use itertools.chain()
to concatenate the two ranges.
import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Create a nice and readable function:
def range_with_negatives(start, end):
for x in range(start, end):
yield x
yield -x
Usage:
list(range_with_negatives(6, 10))
That is how you get a convenient one-liner for anything. Avoid trying to look like a magic pro hacker.
You can use itertools.product
, which is the cartesian product.
[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]
This might be slower because you use multiplication, but should be easy to read.
If you want a purely functional solution, you can also import itertools.starmap
and operator.mul
:
from itertools import product, starmap
from operator import mul
list(starmap(mul, product((-1, 1), range(6, 10))))
However, this is less readable.
If you want to keep the order you’ve specified, you can make use of Python’s built-in range generator with a conditional:
def range_with_negs(start, stop):
for value in range(-(stop-1), stop):
if (value <= -start) or (value >= start):
yield value
Which gives you the output:
In [1]: list(range_with_negs(6, 10))
Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]
And also works with 0 as the start for the full range.
You’re really close, with combining two range
objects. But there is an easier way to do it:
>>> list(range(6, 10)) + list(range(-9, -5))
[6, 7, 8, 9, -9, -8, -7, -6]
That is, convert each range
object to a list, and then concatenate the two lists.
Another approach, using itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]
itertools.chain()
is like a generalized +
: instead of adding two lists, it chains one iterator after another to make a “super-iterator”. Then pass that to list()
and you get a concrete list, with all the numbers you want in memory.
I’d say the simplest solution is to unpack two ranges into a list using the *
unpacking operator:
>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]
Not only is this the shortest answer proposed yet, it’s also the most performant, because it only constructs a single list and involves no function calls beyond the two range
s.
I verified this by testing all of this question’s answers using the timeit
module:
Answer ID Method timeit result -------------------------------------------------------------------------------------------------- (in question) [x for x in range(6,10)] + [y for y in range(-9, -5)] 0.843 usec per loop (this answer) [*range(6, 10), *range(-9, -5)] 0.509 usec per loop 61348876 [y for x in range(6,10) for y in (x,-x)] 0.754 usec per loop 61349149 list(range_with_negatives(6, 10)) 0.795 usec per loop 61348914 list(itertools.chain(range(6, 10), range(-9, -5))) 0.709 usec per loop 61366995 [sign*x for sign, x in itertools.product((-1, 1), range(6, 10))] 0.899 usec per loop 61371302 list(range(6, 10)) + list(range(-9, -5)) 0.729 usec per loop 61367180 list(range_with_negs(6, 10)) 1.95 usec per loop
(timeit testing performed with Python 3.6.9 on my own computer (average specs))
Weighing in with yet another possibility.
If you want readability your original one-liner was pretty good, but I would change the ranges to be the same as I think the negative bounds make things less clear.
[x for x in range(6, 10)] + [-x for x in range(6, 10)]
IMO the approach using itertools.chain
presented in a couple of other answers is definitely the cleanest out of those provided so far.
However, since in your case the order of the values doesn’t matter, you can avoid having to define two explicit range
objects, and thus avoid doing all the off-by-one math necessary for negative range
indexing, by using 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 verbose, but readable enough.
Another similar option is to use tuple/argument unpacking with plain chain
:
>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]
More concise, but I find tuple unpacking harder to grok in a quick scan.
This is a variation on a theme (see @Derte Trdelnik‘s answer) following the philosophy of itertools
where
iterator building blocks […] are useful by themselves or in combination.
The idea is that, while we’re defining a new function, we might as well make it generic:
def interleaved_negatives(it):
for i in it:
yield i
yield -i
and apply it to a particular range
iterator:
list(interleaved_negatives(range(6, 10)))
There can be different ways to get the job done.
Variables given:
1. start=6
2. stop=10
You can try this also, for different approach:
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)
l just like symmetries.
a = 6
b = 10
nums = [x+y for x in (-(a+b-1),0) for y in range(a,b)]
The result is [-9, -8, -7, -6, 6, 7, 8, 9]
.
I believe that the nums expression can be improved, what follows “in” and “range” still looks unbalanced to me.
using itertools:
>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]
itertools.chain()
is like a generalized +
: instead of adding two lists, it chains one iterator after another to make a “super-iterator”. Then pass that to list()
and you get a concrete list, with all the numbers you want in memory.
Seems like only two answers really gave one-liners, so here’s another one:
[i for l in map(lambda x: (x, -x), range(6, 10)) for i in l]
[6, -6, 7, -7, 8, -8, 9, -9]