from itertools import islice
from typing import Iterable, Callable, Sequence, Generator, TypeVar


def chain_call(fns: Iterable[Callable], *initial_call_arguments):
    """
    Simple recipe to chain call functions with matching input and output arguments.
    """
    arguments_signature = len(initial_call_arguments)
    if arguments_signature < 1:
        raise Exception("Cannot chain functions without input")
    elif arguments_signature == 1:
        arg = initial_call_arguments[0]
        for fn in fns:
            arg = fn(arg)
        return arg
    # unpack multiple arguments for calls
    args = initial_call_arguments
    for fn in fns:
        args = fn(*args)
    return args


_SlicedSeqType = TypeVar("_SlicedSeqType")


def multi_islice(
    seq: Sequence[_SlicedSeqType], slices: Sequence[int], start: int, end: int
) -> Generator[Iterable[_SlicedSeqType], None, None]:
    """
    Slice up a sequence with iterators at places determined by a range from another sequence.
    """
    assert end >= start >= 0
    assert len(slices) >= start
    x = 0 if start == 0 else slices[start - 1]
    for i in range(start, min(end, len(slices))):
        t = slices[i]
        yield islice(seq, x, t)
        x = t
    if end > len(slices):
        yield islice(seq, x, len(seq))
