Elegant way for keeping the previous item accessible in a loop through items of a list

83 Views Asked by At

Summary:

In a Python Project I need to apply a function to each two elements of a list of lists that have the same inner index and a neighbouring outer index. The outputs get stored in a new matrix.

The code I've written works, but is not elegant and pyflakes complains about it.

How do I clean up this code?

Additional Info:

The code I'm writing is part of a module that solves a number puzzle.

At one point I am looping through a list of lists of class-instances.
They represent cells in rows in a playing field.
And I need to apply a function to each two vertically neighbouring cells,
and store its output in a new matrix.

Here it doesn't matter which of the cells in a pair is first, but the pairs need to be in order.

Code excerpt:

def func(cell_matrix):
    out_matrix = []
    for y_pos, line in enumerate(cell_matrix):
        out_line = []
        if y_pos != 0:
            for x_pos, cell in enumerate(line):
                out_line.append(compare_func(prev_line[x_pos], cell)
            out_matrix.append(out_line)
        prev_line = line
    return out_matrix

What pyflakes complains about:

Line 7: pyflakes [E]: undefined name 'prev_line'
Line 9: pyflakes [E]: local variable 'prev_line' is assigned to but never used
4

There are 4 best solutions below

3
paul On BEST ANSWER

I would recommend to use only indexes, thus you spare the prev_ variable.

E.g.

def func(cell_matrix):                                                           
    out_matrix = []                                                              
    for y_pos in range(len(cell_matrix)):                                        
        out_line = []                                                            
        if y_pos != 0:                                                           
            for x_pos in range(len(cell_matrix[y_pos])):                         
                out_line.append(compare_func(cell_matrix[y_pos-1][x_pos],        
                                             cell_matrix[y_pos][x_pos]))         
            out_matrix.append(out_line)                                          
    return out_matrix                                                            

But it could be even more simplified by using a comprehension:

def func(cell_matrix):                                                           
    return [[compare_func(                                                       
                 cell_matrix[y_pos-1][x_pos], cell_matrix[y_pos][x_pos])         
             for x_pos in range(len(cell_matrix[y_pos]))]                        
            for y_pos in range(1, len(cell_matrix))]                             

Edit: By the way, the errors you get are pyflakes messages, the code runs fine afaik. (One could argue it's pyflakes' inability to parse the code correctly)

4
Vitor Falcão On

Change your code to:

def func(cell_matrix):
    out_matrix = []
    prev_line = []
    for y_pos, line in enumerate(cell_matrix):
        out_line = []
        if y_pos != 0:
            for x_pos, cell in enumerate(line):
                out_line.append(compare_func(prev_line[x_pos], cell)
            out_matrix.append(out_line)
        prev_line = line
    return out_matrix

You need to declare prev_line out of the for loop scope so it can use it every loop.

0
Patrick Artner On

You need to declare the variable name before you use it:

def func(cell_matrix):
    out_matrix = []
    prev_line = cell_matrix[0]    # use 1st line as prev_line
    for line in cell_matrix[1:]:  # use 2nd to nth line, no y_pos used
                                  # in the following code so no need to enumerate
        out_line = []
            for x_pos, cell in enumerate(line):
                out_line.append(compare_func(prev_line[x_pos], cell) ) # missing )
            out_matrix.append(out_line)
        prev_line = line
    return out_matrix
0
lgott On

I ended up moving the iterating to a seperate function because I find it easier to read this way.

def subsequences(var, r=2):
    """Yield subsequences of var with length r"""
    # subsequences("ABCD") --> AB BC CD
    for index in range(len(var)-r+1):
        yield tuple(var[index+n] for n in range(r))

def func(cell_matrix):
    return [
        [
            compare_func(cell_a, cell_b)
            for cell_a, cell_b in zip(prev_line, line)
        ]
        for prev_line, line in subsequences(cell_matrix)
    ]

It might just be personal taste though.