Replace a positional slice of a string with another value.

    Parameters
    ----------
    start : int, optional
        Left index position to use for the slice. If not specified (None),
        the slice is unbounded on the left, i.e. slice from the start
        of the string.
    stop : int, optional
        Right index position to use for the slice. If not specified (None),
        the slice is unbounded on the right, i.e. slice until the
        end of the string.
    repl : str, optional
        String for replacement. If not specified (None), the sliced region
        is replaced with an empty string.

    Returns
    -------
    Series or Index
        Same type as the original object.

    See Also
    --------
    Series.str.slice : Just slicing without replacement.

    Examples
    --------
    >>> s = pd.Series(['a', 'ab', 'abc', 'abdc', 'abcde'])
    >>> s
    0        a
    1       ab
    2      abc
    3     abdc
    4    abcde
    dtype: object

    Specify just `start`, meaning replace `start` until the end of the
    string with `repl`.

    >>> s.str.slice_replace(1, repl='X')
    0    aX
    1    aX
    2    aX
    3    aX
    4    aX
    dtype: object

    Specify just `stop`, meaning the start of the string to `stop` is replaced
    with `repl`, and the rest of the string is included.

    >>> s.str.slice_replace(stop=2, repl='X')
    0       X
    1       X
    2      Xc
    3     Xdc
    4    Xcde
    dtype: object

    Specify `start` and `stop`, meaning the slice from `start` to `stop` is
    replaced with `repl`. Everything before or after `start` and `stop` is
    included as is.

    >>> s.str.slice_replace(start=1, stop=3, repl='X')
    0      aX
    1      aX
    2      aX
    3     aXc
    4    aXde
    dtype: object

def str_slice_replace(arr, start=None, stop=None, repl=None):
    """
    Replace a positional slice of a string with another value.

    Parameters
    ----------
    start : int, optional
        Left index position to use for the slice. If not specified (None),
        the slice is unbounded on the left, i.e. slice from the start
        of the string.
    stop : int, optional
        Right index position to use for the slice. If not specified (None),
        the slice is unbounded on the right, i.e. slice until the
        end of the string.
    repl : str, optional
        String for replacement. If not specified (None), the sliced region
        is replaced with an empty string.

    Returns
    -------
    Series or Index
        Same type as the original object.

    See Also
    --------
    Series.str.slice : Just slicing without replacement.

    Examples
    --------
    >>> s = pd.Series(['a', 'ab', 'abc', 'abdc', 'abcde'])
    >>> s
    0        a
    1       ab
    2      abc
    3     abdc
    4    abcde
    dtype: object

    Specify just `start`, meaning replace `start` until the end of the
    string with `repl`.

    >>> s.str.slice_replace(1, repl='X')
    0    aX
    1    aX
    2    aX
    3    aX
    4    aX
    dtype: object

    Specify just `stop`, meaning the start of the string to `stop` is replaced
    with `repl`, and the rest of the string is included.

    >>> s.str.slice_replace(stop=2, repl='X')
    0       X
    1       X
    2      Xc
    3     Xdc
    4    Xcde
    dtype: object

    Specify `start` and `stop`, meaning the slice from `start` to `stop` is
    replaced with `repl`. Everything before or after `start` and `stop` is
    included as is.

    >>> s.str.slice_replace(start=1, stop=3, repl='X')
    0      aX
    1      aX
    2      aX
    3     aXc
    4    aXde
    dtype: object
    """
    if repl is None:
        repl = ''

    def f(x):
        if x[start:stop] == '':
            local_stop = start
        else:
            local_stop = stop
        y = ''
        if start is not None:
            y += x[:start]
        y += repl
        if stop is not None:
            y += x[local_stop:]
        return y

    return _na_map(f, arr)