import numpy as np
import typing


def difference_n(
    func: typing.Callable[[float], float], x_array: np.ndarray, start: int, end: int
) -> float:
    if len(x_array) < end + 1:
        return None

    result = 0

    for k in range(start, end + 1):
        prod = 1
        for j in range(start, end + 1):
            if j != k:
                prod *= x_array[k] - x_array[j]
        result += func(x_array[k]) / prod

    return result


def make_forward_interpolate_function(
    func: typing.Callable[[float], float], x_array: np.ndarray
) -> typing.Callable[[float], float]:
    n = len(x_array)
    diffs = [difference_n(func, x_array, 0, k) for k in range(1, n)]

    def forward_interpolate(x: float) -> float:
        L = func(x_array[0])

        for k in range(1, n):
            prod = diffs[k - 1]
            for i in range(k):
                prod *= x - x_array[i]
            L += prod

        return L

    return forward_interpolate


def make_backward_interpolate_function(
    func: typing.Callable[[float], float], x_array: np.ndarray
) -> typing.Callable[[float], float]:
    n = len(x_array)
    diffs = [difference_n(func, x_array, n - k - 1, n - 1) for k in range(1, n)]

    def backward_interpolate(x: float) -> float:
        L = func(x_array[-1])

        for k in range(n - 1):
            prod = diffs[k]
            for i in range(n - 1, n - k - 2, -1):
                prod *= x - x_array[i]
            L += prod

        return L

    return backward_interpolate
