How to replace None in the List with previous value

Viewed 6061

I want to replace the None in the list with the previous variables (for all the consecutive None). I did it with if and for (multiple lines). Is there any way to do this in a single line? i.e., List comprehension, Lambda and or map

And my idea was using the list comprehension but I was not able to assign variables in a list comprehension to set a previous value.

I have got a similar scenario in my project to handle None in such a way, the thing is I don't want to write 10 lines of code for the small functionality.

def none_replace(ls):
    ret = []
    prev_val = None
    for i in ls:
        if i:
            prev_val = i
            ret.append(i)
        else:
            ret.append(prev_val)
    return ret

print('Replaced None List:', none_replace([None, None, 1, 2, None, None, 3, 4, None, 5, None, None]))

Output:

Replaced None List: [None, None, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5]

9 Answers

In Python 3.8 or higher you can do this using the assignment operator:

def none_replace(ls):
    p = None
    return [p:=e if e is not None else p for e in ls]

You can take advantage of lists being mutable

x =[None, None, 1, 2, None, None, 3, 4, None, 5, None, None]
for i,e in enumerate(x[:-1], 1):
    if x[i] is None:
        x[i] = x[i-1]
print(x)

output

[None, None, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5]

You can use the function accumulate() and the operator or:

from itertools import accumulate

list(accumulate(lst, lambda x, y: y or x))
# [None, None, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5]

In this solution you take the element y and the previous element x and compare them using the operator or. If y is None you take the previous element x; otherweise, you take y. If both are None you get None.

Is there any way to do this in a single line? i.e., List comprehension, Lambda and or map

I don't think so because of the late binding closures. Besides, it might not be readable.

I have got a similar scenario in my project to handle None in such a way, the thing is I don't want to write 10 lines of code for the small functionality.

Why do you think it's small? A problem is a problem that needs to be solved. One small function sounds like a good candidate to solve it.

My approach to solve it:

def none_replace(ls: list):
    prev, this = ls[0], None
    assert prev is not None, "First arg can't be None"

    for i in range(1, len(ls)):
        this = ls[i]
        if this is None:
            ls[i] = prev
        prev = this or ls[i]

    return ls

print('Replaced None List:', none_replace(['asd', None, None, 1, 2, None, None, 3, 4, None, 5, None, None]))

Just for completeness, a terrible one-line version for Python < 3.8:

[
    x.__setitem__(i, e if e is not None else x[i-1] if i > 0 else None)
    or x[i]
    for i, e in enumerate(x)
]

That I had to break it into several lines for display here probably shows already that this is horribly unreadable and should not be used.


list.__setitem__ returns None, so I use an or to always return whatever's currently in the list at that location (or x[i]). As a side-effect, the list.__setitem__ sets the i-th element to itself if it's not None, otherwise to the previous value if i is not 0, otherwise None.

Similar to one of our friend's solutions. slightly modified for readability. Instead of enumeration, use range(len())

x =[None, None, 1, 2, None, None, 3, 4, None, 5, None, None]
for i in range(len(x)):
   if x[i] is None:
     x[i] = x[i-1]
print(x) 

Output: [None, None, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5]

For the first value x[-1] to None value. So No errors while executing the program...

Feedback is always appreciated.. :)

for i in range(len(ls)):
   if ls[i] is None:
     ls[i] = ls[i-1]

This sets it to the previous variable if it is None.

You can use a generator to generate proper values. Simply, generators are functions that generates sequence of values and maintain their last state. When generators are called, they are continuing from the last call. We employ this characteristic to access the last visited item in the list. Please see the following code:

L=[None, None, 1, 2, None, None, 3, 4, None, 5, None, None]

def replaceNoneGenerator(L):
    prev_val=None;
    for item in L:
        prev_val=prev_val if item is None else item
        yield prev_val

L1=[item for item in replaceNoneGenerator(L)]

print(L1)

Output: [None, None, 1, 2, 2, 2, 3, 4, 4, 5, 5, 5]

a = [1,2, None, 3,4, None]
print([a[x] if a[x] else a[x-1]  for x in range(len(a))])

This definitely ignores the cases where None is present at the first place in the list but then handling that shouldn't be a daunting task either.

Related