Separating elements from a list in Python depending on a condition
Question:
I have a list of elements and want to separate the elements of the list by a certain condition.
A simple example is a list of numbers and i want to separate the odd from the even ones.
For that could use the filter
builtin like so:
def is_even(x):
# ...
l = [0, 1, 2, 3, 4, 5, 6]
even = list(filter(is_even, l))
odd = list(filter(not is_even, l))
That is a bit error prone if the condition is a bit more complex, because i repeat myself twice in the filter
functions. Is there a more elegant way to achieve this?
Answers:
itertools has a recipe exactly for that:
from itertools import tee, filterfalse
def partition(pred, iterable):
"Use a predicate to partition entries into false entries and true entries"
# partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)
Usage:
odd, even = partition(is_even, l)
You can convert them to lists, but I suggest leaving them as iterators.
If you do not wish to run the predicate twice, you can create 2 lists like so:
def split_predicate(pred, iterable):
"""Split an iterable into two lists based on a predicate.
The predicate will only be called once per element.
Returns:
A tuple of two lists, the first containing all elements for which
the predicate returned True, the second containing all elements for
which the predicate returned False."""
t1, t2 = [], []
for item in iterable:
(t1 if pred(item) else t2).append(item)
return t1, t2
My approach to this is you can make two lists one named [ even ] and the other named [ odd ] and you can make a for loop to filter the original list and add the even value to the [ even ] list and the odd value to the [ odd ] list:
original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
even_list = []
odd_list = []
for value in original_list:
if value % 2 == 0:
even_list.append(value)
else:
odd_list.append(value)
something like this would work fine and I hope this solves your question ✨
A generator version, to be used when pred
does heavy computation and you wish to run on iterables instead of sequences.
Does not hold all values in memory, runs pred
only once for every object.
from collections import deque
from typing import Callable, TypeVar, Iterable
_T = TypeVar('_T')
def iter_split(pred: Callable[[_T], bool],
iterable: Iterable[_T]) -> tuple[Iterable[_T], Iterable[_T]]:
"""Split an iterable into two iterables based on a predicate.
The predicate will only be called once per element.
Returns:
A tuple of two iterables, the first containing all elements for which
the predicate returned True, the second containing all elements for
which the predicate returned False.
"""
iterator = iter(iterable)
true_values: deque[_T] = deque()
false_values: deque[_T] = deque()
def true_generator():
while True:
while true_values:
yield true_values.popleft()
for item in iterator:
if pred(item):
yield item
break
false_values.append(item)
else:
break
def false_generator():
while True:
while false_values:
yield false_values.popleft()
for item in iterator:
if not pred(item):
yield item
break
true_values.append(item)
else:
break
return true_generator(), false_generator()
A thread-proof example for the true_generator()
(lock is shared between both gens):
lock = RLock() # RLock in case pred uses a generator.
def true_generator():
lock.acquire()
while True:
while true_values:
lock.release()
yield true_values.popleft()
lock.acquire()
for item in iterator:
try:
res = pred(item)
except BaseException:
lock.release()
raise
if res:
lock.release()
yield item
lock.acquire()
break
false_values.append(item)
else:
break
lock.release()
I have a list of elements and want to separate the elements of the list by a certain condition.
A simple example is a list of numbers and i want to separate the odd from the even ones.
For that could use the filter
builtin like so:
def is_even(x):
# ...
l = [0, 1, 2, 3, 4, 5, 6]
even = list(filter(is_even, l))
odd = list(filter(not is_even, l))
That is a bit error prone if the condition is a bit more complex, because i repeat myself twice in the filter
functions. Is there a more elegant way to achieve this?
itertools has a recipe exactly for that:
from itertools import tee, filterfalse def partition(pred, iterable): "Use a predicate to partition entries into false entries and true entries" # partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 t1, t2 = tee(iterable) return filterfalse(pred, t1), filter(pred, t2)
Usage:
odd, even = partition(is_even, l)
You can convert them to lists, but I suggest leaving them as iterators.
If you do not wish to run the predicate twice, you can create 2 lists like so:
def split_predicate(pred, iterable):
"""Split an iterable into two lists based on a predicate.
The predicate will only be called once per element.
Returns:
A tuple of two lists, the first containing all elements for which
the predicate returned True, the second containing all elements for
which the predicate returned False."""
t1, t2 = [], []
for item in iterable:
(t1 if pred(item) else t2).append(item)
return t1, t2
My approach to this is you can make two lists one named [ even ] and the other named [ odd ] and you can make a for loop to filter the original list and add the even value to the [ even ] list and the odd value to the [ odd ] list:
original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
even_list = []
odd_list = []
for value in original_list:
if value % 2 == 0:
even_list.append(value)
else:
odd_list.append(value)
something like this would work fine and I hope this solves your question ✨
A generator version, to be used when pred
does heavy computation and you wish to run on iterables instead of sequences.
Does not hold all values in memory, runs pred
only once for every object.
from collections import deque
from typing import Callable, TypeVar, Iterable
_T = TypeVar('_T')
def iter_split(pred: Callable[[_T], bool],
iterable: Iterable[_T]) -> tuple[Iterable[_T], Iterable[_T]]:
"""Split an iterable into two iterables based on a predicate.
The predicate will only be called once per element.
Returns:
A tuple of two iterables, the first containing all elements for which
the predicate returned True, the second containing all elements for
which the predicate returned False.
"""
iterator = iter(iterable)
true_values: deque[_T] = deque()
false_values: deque[_T] = deque()
def true_generator():
while True:
while true_values:
yield true_values.popleft()
for item in iterator:
if pred(item):
yield item
break
false_values.append(item)
else:
break
def false_generator():
while True:
while false_values:
yield false_values.popleft()
for item in iterator:
if not pred(item):
yield item
break
true_values.append(item)
else:
break
return true_generator(), false_generator()
A thread-proof example for the true_generator()
(lock is shared between both gens):
lock = RLock() # RLock in case pred uses a generator.
def true_generator():
lock.acquire()
while True:
while true_values:
lock.release()
yield true_values.popleft()
lock.acquire()
for item in iterator:
try:
res = pred(item)
except BaseException:
lock.release()
raise
if res:
lock.release()
yield item
lock.acquire()
break
false_values.append(item)
else:
break
lock.release()