""" Optional problems for Lab 3 """

from lab03 import *

## Higher order functions

def cycle(f1, f2, f3):
    """Returns a function that is itself a higher-order function.

    >>> def add1(x):
    ...     return x + 1
    >>> def times2(x):
    ...     return x * 2
    >>> def add3(x):
    ...     return x + 3
    >>> my_cycle = cycle(add1, times2, add3)
    >>> identity = my_cycle(0)
    >>> identity(5)
    5
    >>> add_one_then_double = my_cycle(2)
    >>> add_one_then_double(1)
    4
    >>> do_all_functions = my_cycle(3)
    >>> do_all_functions(2)
    9
    >>> do_more_than_a_cycle = my_cycle(4)
    >>> do_more_than_a_cycle(2)
    10
    >>> do_two_cycles = my_cycle(6)
    >>> do_two_cycles(1)
    19
    """
    def how_many(n):
        def what(x):
            if n >= 1:
                x = f1(x)
            if n >= 2:
                x = f2(x)
            if n >= 3:
                x = f3(x)
            if n > 3:
                return how_many(n - 3)(x)
            else:
                return x
        return what
    return how_many

## Lambda expressions

def is_palindrome(n):
    """
    Fill in the blanks '_____' to check if a number
    is a palindrome.

    >>> is_palindrome(12321)
    True
    >>> is_palindrome(42)
    False
    >>> is_palindrome(2015)
    False
    >>> is_palindrome(55)
    True
    """
    x, y = n, 0
    f = lambda: y * 10 + x % 10
    while x > 0:
        x, y = x // 10, f()
    return y == n

## More recursion practice

def skip_mul(n):
    """Return the product of n * (n - 2) * (n - 4) * ...

    >>> skip_mul(5) # 5 * 3 * 1
    15
    >>> skip_mul(8) # 8 * 6 * 4 * 2
    384
    """
    if n <= 0:
        return 1
    else:
        return n * skip_mul(n - 2)

def is_prime(n):
    """Returns True if n is a prime number and False otherwise.

    >>> is_prime(2)
    True
    >>> is_prime(16)
    False
    >>> is_prime(521)
    True
    """
    x = 2
    def divide_x(x):
        if x > round(pow(n, 0.5)):
            return True
        elif n % x == 0:
            return False
        else:
            return divide_x(x + 1)
    return divide_x(x)

def interleaved_sum(n, odd_term, even_term):
    """Compute the sum odd_term(1) + even_term(2) + odd_term(3) + ..., up
    to n.

    >>> # 1 + 2^2 + 3 + 4^2 + 5
    ... interleaved_sum(5, lambda x: x, lambda x: x*x)
    29
    """
    k =  1
    def odd(k):
        if k > n:
            return 0
        else:
            return odd_term(k) + even(k + 1)
    def even(k):
        if k > n:
            return 0
        else:
            return even_term(k) + odd(k + 1)
    return odd(k)


def ten_pairs(n):
    """Return the number of ten-pairs within positive integer n.

    >>> ten_pairs(7823952)
    3
    >>> ten_pairs(55055)
    6
    >>> ten_pairs(9641469)
    6
    """
    def counter(x, n):
        if n == 0:
            return 0
        else:
            if x == n % 10:
                return 1 + counter(x, n // 10)
            else:
                return counter(x, n // 10)
    def one_to_five(k):
        if k == 5:
            return round(counter(k,n) * (counter(k,n) - 1) / 2)
        else:
            return counter(k,n) * counter(10-k, n) + one_to_five(k+1)

    return one_to_five(1)
