Construct a shift function from an alphabet.

    Examples:
        Shift cases independently

        >>> make_shift_function([string.ascii_uppercase, string.ascii_lowercase])
        <function make_shift_function.<locals>.shift_case_sensitive>

        Additionally shift punctuation characters

        >>> make_shift_function([string.ascii_uppercase, string.ascii_lowercase, string.punctuation])
        <function make_shift_function.<locals>.shift_case_sensitive>

        Shift entire ASCII range, overflowing cases

        >>> make_shift_function([''.join(chr(x) for x in range(32, 127))])
        <function make_shift_function.<locals>.shift_case_sensitive>

    Args:
        alphabet (iterable): Ordered iterable of strings representing separate cases of an alphabet

    Returns:
        Function (shift, symbol)

def make_shift_function(alphabet):
    """Construct a shift function from an alphabet.

    Examples:
        Shift cases independently

        >>> make_shift_function([string.ascii_uppercase, string.ascii_lowercase])
        <function make_shift_function.<locals>.shift_case_sensitive>

        Additionally shift punctuation characters

        >>> make_shift_function([string.ascii_uppercase, string.ascii_lowercase, string.punctuation])
        <function make_shift_function.<locals>.shift_case_sensitive>

        Shift entire ASCII range, overflowing cases

        >>> make_shift_function([''.join(chr(x) for x in range(32, 127))])
        <function make_shift_function.<locals>.shift_case_sensitive>

    Args:
        alphabet (iterable): Ordered iterable of strings representing separate cases of an alphabet

    Returns:
        Function (shift, symbol)
    """
    def shift_case_sensitive(shift, symbol):
        case = [case for case in alphabet if symbol in case]
        if not case:
            return symbol

        case = case[0]
        index = case.index(symbol)
        return case[(index - shift) % len(case)]

    return shift_case_sensitive