"""
This module contains functions related to integer formatting and math.
"""

from functools import reduce
from itertools import count
from math import gcd, prod


# ================ ARRAY FORMATTING FUNCTIONS ================

def str_array_to_int(intarray):
    return int(''.join(intarray))


def int_array_to_int(intarray):
    return str_array_to_int(map(str, intarray))


def int_to_int_array(num):
    """
    Deprecated, use int_to_digit_array(num)
    """
    return [int(str(num)[a]) for a in range(len(str(num)))]


def int_to_str_array(num):
    return [str(num)[a] for a in range(len(str(num)))]


def int_to_digit_array(num):
    return [int(str(num)[a]) for a in range(len(str(num)))]


# ================ CALCULATION FUNCTIONS ================

def product(numlist):
    """
    Deprecated since Python 3.8, use math.prod instead
    Also remove functools.reduce
    """
    return reduce(lambda x, y: x * y, numlist, 1)


def factorial(num):
    return prod(list(range(1, num + 1)))


def nCr(n, r):
    return int(prod(range(n-r+1, n+1)) / prod(range(1, r+1)))


def phi(n):
    """
    Returns the value of ϕ(n), or the Euler Totient function.
    """
    return len([x for x in range(1, n) if gcd(n, x) == 1])


# ================ COUNTING FUNCTIONS ================

def counting_summations(values, target):
    """
    Returns the number of ways to write target as the sum of numbers in values.
    """
    csums = [[0 for _ in values]]
    while len(csums) <= target:
        tempsum = [0 for _ in values]
        for a in range(len(values)):
            if values[a] > len(csums):
                break
            elif values[a] == len(csums):
                tempsum[a] = 1
            else:
                tempsum[a] += sum(csums[len(csums) - values[a]][:a+1])
        csums.append(tempsum)
    return sum(csums[target])


def partition():
    """
    Calculates the partition function using Euler's method.
    Much faster than the above function.
    """
    yield 1
    p = [1]
    for i in count(1):
        new_p = 0
        for j in count(1):
            # move i
            if j % 2 == 0:
                i -= j // 2
            else:
                i -= j
            if i < 0:
                break
            # add to new_p
            if (j - 1) % 4 < 2:
                new_p += p[i]
            else:
                new_p -= p[i]
        p.append(new_p)
        yield new_p
