Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I have a for loop to create a list, can I use a list comprehension instead?

Let's have a list of values and an arbitrary integer number.

values = ['5', '3', '.', '.', '7', '.', '.', '.', '.', '6', '.', '.', '1', '9', '5', '.', '.', '.', '.', '9', '8', '.', '.', '.', '.', '6', '.', '8', '.', '.', '.', '6', '.', '.', '.', '3', '4', '.', '.', '8', '.', '3', '.', '.', '1', '7', '.', '.', '.', '2', '.', '.', '.', '6', '.', '6', '.', '.', '.', '.', '2', '8', '.', '.', '.', '.', '4', '1', '9', '.', '.', '5', '.', '.', '.', '.', '8', '.', '.', '7', '9']

n = 9

I'd like to group the values with n numbers in a row.

Let us suppose n=9, that is 9 numbers will be in a row.

The result should be like this:

grouped_values = [
     ['5', '3', '.', '.', '7', '.', '.', '.', '.'],
     ['6', '.', '.', '1', '9', '5', '.', '.', '.'],
     ['.', '9', '8', '.', '.', '.', '.', '6', '.'],
     ['8', '.', '.', '.', '6', '.', '.', '.', '3'],
     ['4', '.', '.', '8', '.', '3', '.', '.', '1'],
     ['7', '.', '.', '.', '2', '.', '.', '.', '6'],
     ['.', '6', '.', '.', '.', '.', '2', '8', '.'],
     ['.', '.', '.', '4', '1', '9', '.', '.', '5'],
     ['.', '.', '.', '.', '8', '.', '.', '7', '9']]

I can do it like this:

def group(values, n):
   rows_number = int(len(values)/n) # Simplified. Exceptions will be caught.
   grouped_values = []

   for i in range(0, rows_number):
      grouped_values.append(values[i:i+9])

But there is a suspicion that list comprehension can be used here. Could you help me understand how can it be done?

like image 373
Michael Avatar asked Dec 14 '22 18:12

Michael


1 Answers

Just move the expression in the list.append() call to the front, and add the for loop:

grouped_values = [values[i:i + 9] for i in range(rows_number)]

Note that this does not slice up your input list into chunks of consecutive elements. It produces a sliding window; you slice values[0:9] then values[1:10], etc. It produces windows onto the input data, each of length 9, with 8 elements overlapping with the previous window. To produce consecutive chunks of length 9, use range(0, len(values), n) as the range, no need to calculate rows_number:

grouped_values = [values[i:i + n] for i in range(0, len(values), n)]

Whenever you see a pattern like this:

<list_name> = []

for <targets> in <iterable>:
    <list_name>.append(<expression>)

where <expression> does not reference <list_name>, you can trivially turn that into

<list_name> = [<expression> for <targets> in <iterable>]

The only difference here is that list_name is not set until after the whole list comprehension has been executed. You can't reference the list being built from inside the list comprehension.

Sometimes you need to move additional code in the loop body that produces that final <expression> value into a single expression before you arrive at the above pattern.

Note that it doesn't matter here that <expression> itself produces list objects; they can be entirely new list comprehensions or any other valid Python expression.

When there are more for loops or if statements with added nested levels, then list those added for loops and if statements from left-to-right in the resulting list comprehension; for example, the pattern

<list_name> = []

for <targets1> in <iterable1>:
    if <test_expression>:
        for <targets2> in <iterable2>:        
            <list_name>.append(<expression>)

becomes

<list_name> = [
    <expression>
    for <targets> in <iterable>
    if <test_expression>
    for <targets2> in <iterable2>
]
like image 191
Martijn Pieters Avatar answered Dec 17 '22 22:12

Martijn Pieters