# 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.

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.