Iterable to get every convolution window per loop iteration.

    For example:
        `convolved([1, 2, 3, 4], kernel_size=2)`
            will produce the following result:
            `[[1, 2], [2, 3], [3, 4]]`.
        `convolved([1, 2, 3], kernel_size=2, stride=1, padding=2, default_value=42)`
            will produce the following result:
            `[[42, 42], [42, 1], [1, 2], [2, 3], [3, 42], [42, 42]]`

    Arguments:
        iterable: An object to iterate on. It should support slice indexing if `padding == 0`.
        kernel_size: The number of items yielded at every iteration.
        stride: The step size between each iteration.
        padding: Padding must be an integer or a string with value `SAME` or `VALID`. If it is an integer, it represents
            how many values we add with `default_value` on the borders. If it is a string, `SAME` means that the
            convolution will add some padding according to the kernel_size, and `VALID` is the same as
            specifying `padding=0`.
        default_value: Default fill value for padding and values outside iteration range.

    For more information, refer to: 
    - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
    - https://github.com/guillaume-chevalier/python-conv-lib
    - MIT License, Copyright (c) 2018 Guillaume Chevalier

def convolved(iterable, kernel_size=1, stride=1, padding=0, default_value=None):
    """Iterable to get every convolution window per loop iteration.

    For example:
        `convolved([1, 2, 3, 4], kernel_size=2)`
            will produce the following result:
            `[[1, 2], [2, 3], [3, 4]]`.
        `convolved([1, 2, 3], kernel_size=2, stride=1, padding=2, default_value=42)`
            will produce the following result:
            `[[42, 42], [42, 1], [1, 2], [2, 3], [3, 42], [42, 42]]`

    Arguments:
        iterable: An object to iterate on. It should support slice indexing if `padding == 0`.
        kernel_size: The number of items yielded at every iteration.
        stride: The step size between each iteration.
        padding: Padding must be an integer or a string with value `SAME` or `VALID`. If it is an integer, it represents
            how many values we add with `default_value` on the borders. If it is a string, `SAME` means that the
            convolution will add some padding according to the kernel_size, and `VALID` is the same as
            specifying `padding=0`.
        default_value: Default fill value for padding and values outside iteration range.

    For more information, refer to: 
    - https://github.com/guillaume-chevalier/python-conv-lib/blob/master/conv/conv.py
    - https://github.com/guillaume-chevalier/python-conv-lib
    - MIT License, Copyright (c) 2018 Guillaume Chevalier
    """
    # Input validation and error messages
    if not hasattr(iterable, '__iter__'):
        raise ValueError(
            "Can't iterate on object.".format(
                iterable))
    if stride < 1:
        raise ValueError(
            "Stride must be of at least one. Got `stride={}`.".format(
                stride))
    if not (padding in ['SAME', 'VALID'] or type(padding) in [int]):
        raise ValueError(
            "Padding must be an integer or a string with value `SAME` or `VALID`.")
    if not isinstance(padding, str):
        if padding < 0:
            raise ValueError(
                "Padding must be of at least zero. Got `padding={}`.".format(
                    padding))
    else:
        if padding == 'SAME':
            padding = kernel_size // 2
        elif padding == 'VALID':
            padding = 0
    if not type(iterable) == list:
        iterable = list(iterable)

    # Add padding to iterable
    if padding > 0:
        pad = [default_value] * padding
        iterable = pad + list(iterable) + pad

    # Fill missing value to the right
    remainder = (kernel_size - len(iterable)) % stride
    extra_pad = [default_value] * remainder
    iterable = iterable + extra_pad

    i = 0
    while True:
        if i > len(iterable) - kernel_size:
            break
        yield iterable[i:i + kernel_size]
        i += stride