nested for loops to list comprehension with differents "if" conditions

Viewed 1447

I'm trying to convert this nested loop into a list comprehension but i'm not sure it's possible as there is different values possible for item in "tmp" list. Is it the best way to do this ? Thanks !

final = []
for a in range(-13, 1):
    for b in range(0,4):
        for c in range(6, 49):
            for d in range(48, 94):
                tmp = [0 for i in range(100)]
                for i in range(100):
                    if raw_x[i] >= b and raw_y[i] >= d:
                        tmp [i] = -1
                    if raw_x[i] <= a and raw_y[i] <= c:
                        tmp [i] = 1
                final.append(tmp)
3 Answers

nested comprehensions are not very readable

a simple

[something for something in container if something > 9]

is awesome, but a nested one is often confusing

you can simply move the loops into a generator function - it will still be readable and allow for lazy iteration

def no_idea_what_this_represents():
    for a in range(-13, 1):
        for b in range(0,4):
            for c in range(6, 49):
                for d in range(48, 94):
                    tmp = [0 for i in range(100)]
                    for i in range(100):
                        if raw_x[i] >= b and raw_y[i] >= d:
                            tmp [i] = -1
                        if raw_x[i] <= a and raw_y[i] <= c:
                            tmp [i] = 1
                    yield tmp

final = [signs for signs in no_idea_what_this_represents()]

EDIT: just an opinionated addendum - this way the complicated nested loop can be named ( I named it no_idea_what_this_represents for obvious reasons ) but when a programmer sees

possible_views = [matrix for matrix in camera_matrices()]

he immediately knows what that means while something like

possible_views = [device.matrix 
                  for devices in itertools.chain(connected_devices(), buffered_devices()
                  for device in devices
                  if device.type=='camera']

makes the programmer read a lot of lines and it is not obvious what is going on

Your algorithm has large time complexity. First see if this is actually what you require. Once you are sure you need a nested for loop with 4 nesting levels, you can define a function to use within your list comprehension.

In this case, notice built-in map can accept multiple iterable arguments. functools.partial allows you to define functions with pre-set arguments.

from functools import partial

def get_val(x, y, a, b, c, d):
    if x <= a and y <= c:
        return 1
    if x >= b and y >= d:
        return -1
    return 0

final = [list(map(partial(get_val, a=a, b=b, c=c, d=d), raw_x, raw_y)) \
         for a in range(-13, 1) \
         for b in range(0, 4) \
         for c in range(6, 49) \
         for d in range(48, 94)]

This can be done in one expression although I'm not sure readability is improved:

final = [
    [
        +1 if x <= a and y <= c
        else -1 if x >= b and y >= d
        else 0
        for x, y in zip( raw_x, raw_y )
    ]
    for a in range(-13, 1)
    for b in range(0, 4)
    for c in range(6, 49)
    for d in range(48, 94)
]

Note that I've assumed you want to go through the whole of raw_x and raw_y rather than exactly 100 elements every time: the question implies 100-every-time but if the intention is really to go through the whole sequence, then it's better not to hard-code the 100 in there. If I'm wrong about the intention, you can change that inner-comprehension for loop to for i in range(100) and use raw_x[i] and raw_y[i] in the conditional expression instead of x and y.

Related