How to make a fill down like process in list of lists?

Question:

I have the following list a with None values for which I want to make a "fill down".

a = [
        ['A','B','C','D'],
        [None,None,2,None],
        [None,1,None,None],
        [None,None,8,None],
        ['W','R',5,'Q'],
        ['H','S','X','V'],
        [None,None,None,7]
    ]

The expected output would be like this:

b = [
        ['A','B','C','D'],
        ['A','B',2,'D'],
        ['A',1,'C','D'],
        ['A','B',8,'D'],
        ['W','R',5,'Q'],
        ['H','S','X','V'],
        ['H','S','X',7]
    ]

I was able to make the next code and seems to work but I was wondering if there is a built-in method or more direct
way to do it. I know that there is something like that using pandas but needs to convert to dataframe, and I want
to continue working with list, if possible only update a list, and if not possible to modify a then get output in b list. Thanks

b = []
for z in a:
    if None in z:
        b.append([temp[i] if value == None else value for i, value in enumerate(z) ])
    else:
        b.append(z)
        temp = z
Asked By: Rasec Malkic

||

Answers:

You could use a list comprehension for this, but I’m not sure it adds a lot to your solution already.

b = [a[0]]
for a_row in a[1:]:
    b.append([i if i else j for i,j in zip(a_row, b[-1])])

I’m not sure if it’s by design, but in your example a number is never carried down to the next row. If you wanted to ensure that only letters are carried down, this could be added by keeping track of the letters last seen in each position. Assuming that the first row of a is always letters then;

last_seen_letters = a[0]
b = []
for a_row in a:
    b.append(b_row := [i if i else j for i,j in zip(a_row, last_seen_letters)])
    last_seen_letters = [i if isinstance(i, str) else j for i,j in zip(b_row, last_seen_letters)]
Answered By: bn_ln

First, consider the process of "filling down" into a single row. We have two rows as input: the row above and the row below; we want to consider elements from the two lists pairwise. For each pair, our output is determined by simple logic – use the first value if the second value is None, and the second value otherwise:

def fill_down_new_cell(above, current):
    return above if current is None else current

which we then apply to each pair in the pairwise iteration:

def fill_down_new_row(above, current):
    return [fill_down_new_cell(a, c) for a, c in zip(above, current)]

Next we need to consider overlapping pairs of rows from our original list. Each time, we replace the contents of the "current" row with the fill_down_row result, by slice-assigning them to the entire list. In this way, we can elegantly update the row list in place, which allows changes to propagate to the next iteration. So:

def fill_down_inplace(rows):
    for above, current in zip(rows, rows[1:]):
        current[:] = fill_down_new_row(above, current)

Let’s test it:

>>> a = [
...         ['A','B','C','D'],
...         [None,None,2,None],
...         [None,1,None,None],
...         [None,None,8,None],
...         ['W','R',5,'Q'],
...         ['H','S','X','V'],
...         [None,None,None,7]
...     ]
>>> fill_down_inplace(a)
>>> import pprint
>>> pprint.pprint(a)
[['A', 'B', 'C', 'D'],
 ['A', 'B', 2, 'D'],
 ['A', 1, 2, 'D'],
 ['A', 1, 8, 'D'],
 ['W', 'R', 5, 'Q'],
 ['H', 'S', 'X', 'V'],
 ['H', 'S', 'X', 7]]
Answered By: Karl Knechtel
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.