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 ?
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_value
s 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']
I don’t think that the purpose of the program is to
strip
, it looks more a rearrangement