Luca
Luca

Reputation: 10996

python: create lists dynamically

I am trying to create a list of 3D indexes in python. One way I can do is iterate over all the ids in nested loops and do as follows:

l = list()

for z in range(0, 2):
    for y in range(1, 5);
        for x in range(1, 10):
            l.append((x, y, z))

However, I remember there was a one liner way to do this but for the life of me I cannot figure it out. I tried something like:

l.append((x, y, z) for x in range(0, 10), for y in range(0, 10), for z in range(0, 2))

However, this resulted in a syntax error.

Upvotes: 2

Views: 13019

Answers (2)

thefourtheye
thefourtheye

Reputation: 239443

You are getting SyntaxError, because

(x, y, z) for x in range(0, 10), for y in range(0, 10), for z in range(0, 2)

you separate the for expressions with commas. But as per the grammar for list comprehension,

comprehension ::=  expression comp_for
comp_for      ::=  "for" target_list "in" or_test [comp_iter]
comp_iter     ::=  comp_for | comp_if
comp_if       ::=  "if" expression_nocond [comp_iter]

commas should not be there.

Now, even after removing the commas, your program will have a logic error

>>> l = []
>>> l.append((x, y, z) for x in range(10) for y in range(10) for z in range(2))
>>> l
[<generator object <genexpr> at 0x7f5b78302b40>]

Here, the expression (x, y, z) for x in range(0, 10) for y in range(0, 10) for z in range(0, 2) is treated as a generator expression. So, you need to create a list, with list function, like this

>>> list((x, y, z) for x in range(1) for y in range(2) for z in range(2))
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]

Instead, you can use list comprehension to create a new list, like this

>>> [(x, y, z) for x in range(1) for y in range(2) for z in range(2)]
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]

But, in this case, you can use itertools.product, as well

>>> from itertools import product
>>> list(product(range(1), range(2), range(2)))
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]

product function returns a generator, to get the actual entire list, we use list function.

PS: You don't have use the starting value in range function if it is zero, as that is the default value.

Note: What you are actually trying to do is called generating the cartesian product of three iterables. The result will have huge number of elements. So, unless you need all the elements at once, you can simply iterate the product's result. This will greatly improve the program's performance as the entire result need not have to computed immediately.

>>> for items in product(range(1), range(2), range(2)):
...     print(items)
...     
... 
(0, 0, 0)
(0, 0, 1)
(0, 1, 0)
(0, 1, 1)

Now, the values are generated only during their respective iterations.

Upvotes: 7

Simeon Visser
Simeon Visser

Reputation: 122336

You meant to write:

l = [(x, y, z) for x in range(0, 10) for y in range(0, 10) for z in range(0, 2)]

The commas are not necessary after the range() calls and you can directly assign the result (a list) to the variable l.

Upvotes: 7

Related Questions