def reading_csv(path, header = False, delimiter = ','):
    """Считывание X, Y из файла
    
    path - путь к файлу
    header - заголовки
    delimiter - разделитель"""
    
    from pandas import read_csv
    
    if header:
        points = read_csv(path, names = header, sep = delimiter)
        return [i for i in points[list(points)[0]]], [i for i in points[list(points)[1]]]
    else:
        points = read_csv(path, sep = delimiter)

        return [i for i in points[list(points)[0]]], [i for i in points[list(points)[1]]]
    
    
def factor(n):
    """Факториал N
    return N!"""
    r = 1
    for i in range(1,n+1):
        r = r*i   
    return r

def check_step(x):
    """Проверка Шага выборки"""
    s = x[1] - x[0]
    return s
    
def mnogochlen(x, y):
    """Функция принимающая x, y и выдающая ответ
    
    x - x
    y - y"""
    
    from scipy.linalg import solve
    from numpy import array, reshape
    main_mtrx = []
    for i in x:
        main_mtrx.append([i**(el) for el in range(len(x))])
    main_mtrx = array(main_mtrx)
    y = [[i] for i in y]
    y = array(y)
    answ = solve(main_mtrx, y)
    
    return answ    

def answer(x, y, answ, flag):
    """Выдает ответ, и Многочлен
    x, y = x, y
    answ - ответ 
    flag - влияет на выдачу ответа
    Либо просто x, y 
    либо график"""
    import matplotlib.pyplot as plt
    h = ''
    answ = [float(el[0]) for el in answ]
    
    for ind, el in enumerate(answ[::-1]):
        h += f'+{el}*(x**{len(answ) - ind-1})'
    x1 = []
    y1 = []
    for i in range(int(min(x))*10, int(max(x))*10 + 1):
        x1.append(i/10)
        y1.append(eval(h.replace('x', str(i/10))))
    if flag:
        print('Многочлен: ' + h)
        for ind, el in enumerate(x):
            print('x: '+ str(el)+' '+'y: '+ str(y[ind])+ ' ' + 'fi: ' + str(round(eval(h.replace('x', str(el))))))

        plt.scatter(x, y)
        plt.plot(x1,y1)
    else:
        return [x1, y1] 
    
def lagr(path, header = False, delimiter = ',', flag = True):
    
    """Объединение метода Answer, для красоты
    
    x, y = x, y
    answ - ответ 
    flag - влияет на выдачу ответа
    Либо просто x, y 
    либо график
    """
    
    x, y = reading_csv(path = path, delimiter = delimiter)
    return answer(x, y, mnogochlen(x, y), flag = flag)

def table(x, y):
    """строит таблицу ньютона для вычисления кэфов методом Ньютона
    
    x, y = x, y
    """
    import numpy as np
    table1 = [[0 for j in range(len(x))] for i in range(len(x)+1)]
    table1[0], table1[1] = x, y 
    for ind, el in enumerate(table1[2:]):
        for i in range(len(el)-ind-1):
            table1[ind+2][i] = -(table1[ind+1][i] - table1[ind+1][i+1])
    table1 = np.array(table1).T
    return table1



def newton_F(x, y, table):
    """Функция Ньютон вперед
    
    X, Y = X, Y
    table - Таблица Ньютона (метод table)
    
    return - многочлен Ньютона
    """
    from sympy import simplify, expand, Symbol
    P = ''
    h = check_step(x)
    
    for i in range(1, len(y) + 1):
        skobochki = ''
        for j in range(1, i):
            skobochki += f'*(x - {table[j-1][0]})'
            
        helpi = f'{table[0][i]}/({factor(i-1)}*{h**(i-1)})'
        if i == 1:
            P += f'({helpi}){skobochki}'
        else:
            P += f'+({helpi}){skobochki}'
            

    return P

def newton_S(x, y, table):
    """Функция Ньютон назад
    
    X, Y = X, Y
    table - Таблица Ньютона (метод table)
    
    return - многочлен Ньютона"""

    from sympy import simplify, expand, Symbol
    P = ''
    h = check_step(x)
    
    for i in range(1, len(y)+1):
        skobochki = ''
        for j in range(1, i):
            skobochki += f'*(x - {table[len(y)-j][0]})'
        
        helpi = f'{table[len(y) - i][i]}/({factor(i-1)}*{h**(i-1)})'
        if i == 1:
            P += f'({helpi}){skobochki}'
        else:
            P += f'+({helpi}){skobochki}'

    return P
            
            
def newton(path, header = False, delimiter = ',', FoS = 'F', flag = True):
    """
    Функция объединяющая эти два метода (Newton_F, Newton_S)
    в один посредствам флага 

    path - Путь к X, Y
    
    """
    import matplotlib.pyplot as plt
    x, y = csv(path = path, delimiter = delimiter)
    if FoS == 'F':
        F = newton_F(x, y, table(x, y))
    if FoS == 'S':
        F = newton_S(x, y, table(x, y))
    

    x1 = []
    y1 = []
    for i in range(int(min(x))*10, int(max(x))*10+1):
        x1.append(i/10)
        y1.append(eval(F.replace("x", str(i/10))))

    if flag:
        for ind, el in enumerate(x):
            print('x: '+ str(el)+' '+'y: '+ str(y[ind])+ ' ' + 'fi: ' + str(eval(F.replace("x", str(i/10)))))
        print(F)
        
        plt.plot(x1,y1)
        plt.scatter(x, y)
    else:
        return [x1, y1]

def search_C(x, y):
    from scipy.linalg import solve
    from numpy import vstack, array
    """ Поиск С по формуле и
    генерируемой таблице
   
    x, y = x, y"""
    from scipy.linalg import solve
    from numpy import vstack, array
    
    h = check_step(x)
    X = [[4*h if i == j else 0 for j in range(len(x)-2)]for i in range(len(x) - 2)]
    
    for i in range(len(X)):
        try:
            X[i][i+1] = h
        except:
            pass
        try:
            X[i+1][i] = h
        except:
            pass
    
    Y = [[(3/h)*(y[i+2]-2*y[i+1]+y[i])] for i in range(len(x)-2)]
    
    return vstack(([0],array(solve(X,Y))))


def search_ABD(x, y, c):
    
    """Поиск Остальных кэфов через С
    x, y = x, y 
    c = search_C(x, y)
    """
    h = check_step(x)
    
    c = [float(el[0]) for el in c]
    c += [0]
    a = y[:-1]
    d = [(c[i+1]-c[i])/3*h for i in range(len(c)-1)] 
    
    b = [(y[i]-y[i-1])/h-(c[i+1]+2*c[i])*h/3 for i in range(len(c)-1)]
    del c[-1]
    
    return [a,b,c,d]

def splain(path):
    """Генерация формул и вычисление значений в точках
    path - путь к x, y
    """
    x, y = csv(path)
    
    abcd = search_ABD(x, y, search_C(x, y))[::-1]
    for i in range(len(abcd[0])):
        helpi = f'{abcd[0][i]}*(x-{x[i]})**3+{abcd[1][i]}*(x-{x[i]})**3+{abcd[2][i]}*(x-{x[i]})**3+{abcd[3][i]}'
        print('x: '+str(x[i+1])+' '+'y: '+str(y[i])+' fi: '+str(eval(helpi.replace('x', str(x[i]))))+"  "+helpi)
        

def kx(path,delim = ',', flag = True):
    """Апроксимация Прямой
    
    path - Путь к x, y
    flag - Влияет на вывод ответа
    Либо Просто точки,
    Либо Графики"""
    x, y = csv(path, delimiter=delim)
    from scipy.linalg import solve
    import matplotlib.pyplot as plt
#     Решаем слау для поиска кэфов
    kx = solve([[sum([i**2 for i in x]),sum(x)],[sum(x), len(x)]],[[sum([y[i]*x[i] for i in range(len(x))])],[sum(y)]])
    x1 = []
    y1 = []
#     генерируем точки
    for i in range(int(min(x)), int(max(x))+1):
        x1.append(i)
        y1.append(kx[0][0]*i+kx[1][0])
#         выводим
    if flag: 
        print(f'y = {kx[0][0]}x+{kx[1][0]}')
        print()
        for ind, el in enumerate(x):
            print(f'x:{el} y:{y[ind]} fi:{round(kx[0][0]*el+kx[1][0])}')
        print()
        print(f'Дисперсия {sum([(y[ind] - kx[0][0]*el+kx[1][0])**2 for ind, el in enumerate(x)])}')
        plt.plot(x1, y1)
        plt.scatter(x, y)
    else:
        return [x1, y1]

def ax2(path, flag = True):
    """Апроксимация Квадратичной функцией
    
    path - Путь к x, y
    flag - Влияет на вывод ответа
    Либо Просто точки,
    Либо Графики
    """
    x, y = csv(path)
    from scipy.linalg import solve
    import matplotlib.pyplot as plt
    kx = solve([[sum([i**4 for i in x]),sum([i**3 for i in x]),sum([i**2 for i in x])],
                [sum([i**3 for i in x]),sum([i**2 for i in x]),sum([i**1 for i in x])],
                [sum([i**2 for i in x]),sum([i**1 for i in x]),len(x)]],
                [[sum([(x[i]**2)*y[i] for i in range(len(x))])],
                 [sum([x[i]*y[i] for i in range(len(x))])],
                 [sum([y[i] for i in range(len(x))])]])
    
    x1 = []
    y1 = []
    for i in range(int(min(x))*10, (int(max(x))+1)*10):
        x1.append(i/10)
        y1.append(kx[0][0]*(i/10)**2+kx[1][0]*i/10+kx[2][0])
    if flag:
        print(f'y = {kx[0][0]}x**2+{kx[1][0]}x + {kx[2][0]}')
        print()
        for ind, el in enumerate(x):
            print(f'x:{el} y:{y[ind]} fi:{kx[0][0]*(el)**2+kx[1][0]*el+kx[2][0]}')
        print()
        print(f'Дисперсия {sum([(y[ind] - kx[0][0]*(x[ind])**2+kx[1][0]*x[ind]+kx[2][0])**2 for ind, el in enumerate(x)])}')
        plt.plot(x1, y1)
        plt.scatter(x, y)
    else:
        return [x1, y1]

def norm_v2(path, flag = True):
    """Апроксимация Нормальным распределением
    
    path - Путь к x, y
    flag - Влияет на вывод ответа
    Либо Просто точки,
    Либо Графики"""
    from math import log, exp
    from numpy import array, dot, linalg, arange
    import matplotlib.pyplot as plt
    
    x, y = reading_csv(path)
    X = []
    Y = []
    for ind, el in enumerate(x):
        X.append([1, el, el**2])
        Y.append([log(y[ind])])
    B = dot(dot(linalg.inv(dot(array(X).T,array(X))), array(X).T), array(Y))
    
    a = exp(B[0][0] - (B[1][0]**2)/(4*B[2][0]))
    b = - (1/(B[2][0]))
    c = - (B[1][0]/(B[2][0]*2))
    
    f = lambda x: a * exp(-((x - c)**2/b))
    
    x1 = []
    y1 = []
    for el in arange(min(x), max(x), 0.1):
        x1.append(el)
        y1.append(f(el))
    plt.plot(x1, y1, color = 'orange')
    plt.scatter(x, y)

def numpy_poli(path, flag):
    """Выдает точки через Нумпай Полифит
    Path - Путь к файлу
    return точки для построения """
    x, y = csv(path = path)
    from numpy import polyfit
    import matplotlib.pyplot as plt
    x1 = []
    y1 = []
    formula = ''
    m = polyfit(x, y, 2)
    for ind, el in enumerate(m):
        formula += f'+{el}*x**{len(m)-ind-1}'
    for x_el in range((int(min(x)))*10, (int(max(x))+1)*10):
        x1.append(x_el/10)
        y1.append(eval(formula.replace("x", str(eval(f'{x_el}/10')))))
    return [x1, y1]
    
def scipy_interpol(path, flag):
    """Выдает точки через Нумпай Полифит
    Path - Путь к файлу
    return точки для построения """
    x, y = csv(path = path)
    from scipy import interpolate
    from numpy import arange
    import matplotlib.pyplot as plt
    f = interpolate.interp1d(x, y, kind = 'cubic')
    xnew = arange(min(x), max(x), 0.1)
    
    ynew = f(xnew)
    return [xnew, ynew]
    
def comparison(path):
    """Объединение всех функций интерполяции и Апроксимации
    path - путь к точкам"""
    x, y = csv(path = path)
    import matplotlib.pyplot as plt
    fig, axes = plt.subplots(2, 4)
    
    function_list = [[lagrange, scipy_interpol], [kx, ax2, numpy_poli]]
    for ind, i_a in enumerate(function_list):
        for i, func in enumerate(i_a):
#             print(func)
            m = func(path = path, flag = False)
            axes[ind][i].set_title(func.__name__)
            axes[ind][i].plot(m[0], m[1])
            axes[ind][i].scatter(x, y, color = 'orange')
#             print(func.__name__)
    m = newton(path, FoS = 'F', flag =False)
    axes[0][2].set_title('Newton F')
    axes[0][2].plot(m[0], m[1])
    axes[0][2].scatter(x, y, color = 'orange')
#     print('newton >')
    m = newton(path, FoS = 'S', flag =False)
    axes[0][3].set_title('Newton S')
    axes[0][3].plot(m[0], m[1])
    axes[0][3].scatter(x, y, color = 'orange')
#     print('newton <')
    m = al_aprr(path, 'c1 * E**(-((x-c2)**2)/(c3**2))', [c1, c2, c3], flag = False)
    axes[1][3].set_title('Нормальное')
    axes[1][3].plot(m[0], m[1])
    axes[1][3].scatter(x, y, color = 'orange')
    
    fig.set_figheight(10)
    fig.set_figwidth(16)