Itertools.groupby for overlapping groups?

Is there an analogue for this in more-itertools or the standard library?

from typing import Callable, Iterable, Sequence, Tuple, TypeVar
from itertools import tee, filterfalse

T = TypeVar("T")
Predicate = Callable[[T], bool]

T = TypeVar("T")
def group_multiple(items: Iterable[T], 
                   keys: Sequence[Predicate[T]]
) -> Tuple[Iterable[T], ...]:
    """
    Returns a tuple of size ``len(keys)`` where each element
    of index ``n`` is a iterable that yields values where
    ``keys[n](next(element))`` is True.

    This is useful when grouping elements by attributes that are not
    mutually exclusive, meaning that a single element in the iterable
    can appear a single time in multiple iterables.
    """
    iters = tee(iter(items), len(keys))
    return (*(filterfalse(key, iter_) for key, iter_ in zip(keys, iters)),)

With this, one can generate all the vertices for the faces of a unit cube quite easily

# cartesian product
from itertools import product

vx0, vx1, vy0, vy1, vz0, vz1 = group_multiple(
    product([0, 1], repeat=3),
    (*(lambda point, dim=dim, val=val: point[dim] == val \
        # for planes x==0, x==1, y==0, y==1, z==0, z==1 
        for dim, val in product(range(3), [1, 0])),)
)

[list(iter_) for iter_ in (vx0, vx1, vy0, vy1, vz0, vz1)]

The vertices can later be sorted using scipy.cluster.hierarchy.linkage if necesary.

Your Haskellian style is a foreign language to me, but it seems to me that this is just a complicated way of writing:

def group_multiple(items, keys):
    return (filterfalse(key, items) for key in keys)

items would need to be a sequence then, not an interator. But yes, that’s the gist of it. I noticed that it is not much pass the boilerplate

items just needs to be an iterable, not a sequence. I guess your version will actually take any iterator, and you could strengthen the type to items: Iterator[T].

I made the distinction between a sequence and an iterable since iterators are themselves iterables, and iterators do not need to return a new iterator each time you call iter on it, they usually return themselves. So your code would reuse the exhausted iterator as many as len(keys-1)-1, only yielding values for the first key. From what I understand you need to call tee on every iterable you plan on reusing unless you specifically know the underlying type.

from Glossary — Python 3.12.1 documentation :

Iterators are required to have an __iter__() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.

An example of a non-sequence, non-iterator iterable that works just fine with my code is a set. This is a case of terminology not being fine-grained enough to express all the distinctions. There are three kinds of iterasomethings, but only two words for them.

Again, I used sequence as an example of a more specific type. tee is necessary even for iterables, otherwise the code breaks when you pass iterators.

I wasn’t suggesting that the simpler version was a complete replacement. I was just trying to understand what your function was supposed to do, and re-implementing and comparing results was a way to check that I had understood your docstring.

The function name and the use of filterfalse instead of filter is a mystery to me: In what way does this group multiple? I would have called such a function parallel_filter or multifilter if not for the reversed polarity.