How to shift the values of a list to the left as much as possible?

Let’s consider these two lists :

list1 = ['N/A', 'a', 'b', 'c']
list2 = [None, None, 'd', None, 'e']

1-) I need to shift their items to the left as long as there is a target value at the beginning but 2-) we should ignore the eventual targets that can be in the middle and finally 3-) we fill the replacements with a value from the end.

In a parallel universe, the function I need would be a method of the python’s list: list.lstrip(target_value, fill_value)

I tried to make it real with the code below but it doesn’t give the expected output for list2 :

def lstrip(a_list, target_value=None, fill_value="Invalid"):
    if a_list[0] != target_value:
        return a_list
    else:
        result = []
        for element in a_list:
            if element != target_value:
                result.append(element)
        return result + [fill_value]*(len(a_list) - len(result))

The current outputs are :

print(lstrip(list1, 'N/A'))
['a', 'b', 'c', 'Invalid']

print(lstrip(list2))
['d', 'e', 'Invalid', 'Invalid', 'Invalid']

The second output should be ['d', None, 'e', 'Invalid', 'Invalid'].

Do you guys have an idea how to fix my code ?

  • I don’t think that the purpose of the program is to strip, it looks more a rearrangement

    – 

It seems like you could just find the index of the first non_target value and return the slice from that point on plus an array of fill values of the same length as the index:

list1 = ['N/A', 'a', 'b', 'c']
list2 = [None, None, 'd', None, 'e']


def lstrip(a_list, target_value=None, fill_value="Invalid"):
    i = next((i for i, v in enumerate(a_list) if v != target_value), len(a_list))
    return a_list[i:] + [fill_value] * i
    
    
print(lstrip(list1, 'N/A'))
# ['a', 'b', 'c', 'Invalid']

print(lstrip(list2))
# ['d', None, 'e', 'Invalid', 'Invalid']

Giving the length of the list as a default to next() covers the case where a list is all target values:

lstrip([None, None])
# ['Invalid', 'Invalid']

The issue with your implementation is that the condition if element != target_value: continues to filter out target_values even after an element that does not match the target value has been seen. You can fix this by adding a simple boolean flag for when a mismatch is found:

def lstrip(a_list, target_value=None, fill_value="Invalid"):
    if a_list[0] != target_value:
        return a_list
    else:
        seen_mismatch = False
        result = []
        for element in a_list:
            if element != target_value:
                seen_mismatch = True
            if seen_mismatch:
                result.append(element)
        return result + [fill_value] * (len(a_list) - len(result))

Here’s an alternative implementation that makes use of partially consuming an iterator over the list. This also generalizes the function to work on empty lists and over other arbitrary iterables.

from collections.abc import Iterable, Iterator
from typing import TypeVar, Literal

_T = TypeVar("_T")


def lstrip(
    it: Iterable[_T],
    target_value: _T | None = None,
    fill_value: _T | Literal["Invalid"] = "Invalid",
) -> Iterator[_T]:
    it = iter(it)
    backfill = 0
    for elem in it:
        if elem == target_value:
            backfill += 1
        else:
            yield elem
            break

    yield from it
    yield from [fill_value] * backfill

>>> print(list(lstrip(list1, "N/A")))
['a', 'b', 'c', 'Invalid']


>>> print(list(lstrip(list2)))
['d', None, 'e', 'Invalid', 'Invalid']

Leave a Comment