fluttersmith/py-snippetsGitHub ↗

Split a list in two with a predicate

Partition items into those that pass a test and those that don't, in one pass.

Lists#splittingO(n) time✓ 4 examples pass on 3.11–3.14

Two list comprehensions work, but they walk the data twice and call your test twice per item. That’s slow for an expensive check and wrong for a one-shot iterator.

bifurcate_by.py✓ ruff · mypy --strict
from collections.abc import Callable, Iterable
from typing import TypeVar

T = TypeVar("T")


def bifurcate_by(
    items: Iterable[T], predicate: Callable[[T], object]
) -> tuple[list[T], list[T]]:
    """Split ``items`` into the ones that pass ``predicate`` and the ones that don't."""
    passed: list[T] = []
    failed: list[T] = []
    for item in items:
        (passed if predicate(item) else failed).append(item)
    return passed, failed
  1. one pass, so predicate runs once per item
examples✓ 4 doctests · 3.11–3.14
passed on Python 3.11 to 3.14: >>> bifurcate_by(["beep", "boop", "foo", "bar"], lambda w: w.startswith("b"))(['beep', 'boop', 'bar'], ['foo'])passed on Python 3.11 to 3.14: >>> passed, failed = bifurcate_by(range(10), lambda n: n % 3 == 0)passed on Python 3.11 to 3.14: >>> passed, failed([0, 3, 6, 9], [1, 2, 4, 5, 7, 8])passed on Python 3.11 to 3.14: >>> bifurcate_by([], bool)([], [])
Edit the function or the examples and run the doctests here, on Python 3.14. Python loads only when you click (about 7 MB, cached after the first time).

How it works

  • One loop. The conditional expression picks which list to append to.
  • Order is kept within each half.
  • It returns a tuple, so passed, failed = ... unpacks it.

Complexity: O(n) time, O(n) space

Importfrom pysnippets.lists.bifurcate_by import bifurcate_byNo dependencies: copy bifurcate_by.py into your project, or clone the repo.

Adapted from bifurcate-by in 30-seconds-of-python by the 30 seconds of code contributors, CC BY 4.0. Changed: Rewritten: single pass, returns a tuple, accepts any iterable, typed. Suggest an edit.