Лаб. 02: Лямбда-выражения и функции высшего порядка

Основная часть

Эту часть практических вопросов нужно успеть сделать на занятии.

Проверять правильность ответов на ПСП-вопросы можно и нужно из терминала вот таким образом:

$ python3 lab_02.py

Если что-то не так, будет выведено сообщение об ошибке с указанием функции и номера строки в файле.

Вопрос 1: Лямбда-выражения (ПСП)

Подумай и запиши, что выведет Python при обработке следующих выражений.

Если значение выражения является функцией, то вывод интерпретатора будет примерно такой: <function abc at 0x103277f28>. Предугадать, какие именно цифры окажутся после 0x невозможно. Поэтому, если какое-нибудь выражение в заданиях ниже будет равно функции, то достаточно записать:

<function ...
>>> lambda x: x
______
>>> a = lambda x: x
>>> a(5)
______
>>> b = lambda: 3
>>> b()
______
>>> c = lambda x: lambda: print('123')
>>> c(88)
______
>>> c(88)()
______
>>> d = lambda f: f(4)
>>> def square(x):
...     return x * x
>>> d(square)
______
>>> z = 3
>>> e = lambda x: lambda y: lambda: x + y + z
>>> e(0)(1)()
______
>>> f = lambda z: x + z
>>> f(3)
______
>>> higher_order_lambda = lambda f: lambda x: f(x)
>>> g = lambda x: x * x
>>> higher_order_lambda(2)(g)
______
>>> higher_order_lambda(g)(2)
______
>>> call_thrice = lambda f: lambda x: f(f(f(x)))
>>> call_thrice(lambda y: y + 1)(0)
______
>>> print_lambda = lambda z: print(z)
>>> print_lambda
______
>>> one_thousand = print_lambda(1000)
______
>>> one_thousand
______

Помнишь?

$ git add .
$ git commit -m "Решение вопроса 1"

Вопрос 2: Функции высшего порядка (ПСП)

>>> def even(f):
...     def odd(x):
...         if x < 0:
...             return f(-x)
...         return f(x)
...     return odd
>>> janet = lambda x: x
>>> brad = even(janet)
>>> brad
______
>>> brad(61)
______
>>> brad(-4)
______
>>> def cake():
...    print('гадость')
...    def pie():
...        print('сладость')
...        return 'торт'
...    return pie
>>> chocolate = cake()
______
>>> chocolate
______
>>> chocolate()
______
>>> more_chocolate, more_cake = chocolate(), cake
______
>>> more_chocolate
______
>>> def snake(x, y):
...    if cake == more_cake:
...        return lambda: x + y
...    else:
...        return x + y
>>> snake(10, 20)
______
>>> snake(10, 20)()
______
>>> cake = 'торт'
>>> snake(10, 20)
______

Не забывай.

$ git add .
$ git commit -m "Решение вопроса 2"

Вопрос 3: Каррирование лямбдой

Проверять правильность кода в следующих вопросах нужно так:

$ python3 -m doctest lab_02.py

Функцию множества аргументов можно превратить в цепочку функций высшего порядка, каждая из которых — функция одного аргумента.

Напиши функцию lambda_curry2, которая будет производить такое преобразование для функции двух аргументов. Посмотри на доктест, если задание не очень понятно. Решение должно занимать всего одну строку.

Можешь сначала решить задачу без ограничения по размеру кода, а потом, когда логика будет понятна, переписать как требуется.
def lambda_curry2(func):
    """
    Возвращает каррированную версию функции func от двух аргументов.

    >>> from operator import add
    >>> x = lambda_curry2(add)
    >>> y = x(3)
    >>> y(5)
    8
    """
    return ______

Обязательно.

$ git add .
$ git commit -m "Решение вопроса 3"

Вопрос 4: Лямбды на диаграммах окружения

Попробуй нарисовать диаграмму окружения для следующего фрагмента кода и предугадать, что получится в итоге.

Решение этого задания не нужно заносить в файл lab_02.py. Просто нарисуй диаграмму окружения на листе бумаги, а потом проверь себя с помощью сайта Online Python Tutor.

>>> a = lambda x: x * 2 + 1
>>> def b(b, x):
...     return b(x + a(x))
>>> x = 3
>>> b(a, x)
______

Дополнительная часть

Эту часть, если есть время, можно поделать и дома.

Вопрос 5: Равенство композиции

Напиши функцию, которая принимает две функции одного аргумента — f и g — и возвращает другую функцию одного аргумента x. Эта функция должна возвращать True, если f(g(x)) и g(f(x)) равны. Можешь считать, что результат g(x) можно использовать аргументом для f и наоборот.

Попробуй в решении использовать compose1, приведённую ниже для справки.
def compose1(f, g):
    """
    Возвращает функцию h такую, что h(x) = f(g(x)).

    >>> add_one = lambda x: x + 1        # прибавляет единицу к x
    >>> square = lambda x: x**2
    >>> a1 = compose1(square, add_one)   # (x + 1)^2
    >>> a1(4)
    25
    >>> mul_three = lambda x: x * 3      # умножает 3 на x
    >>> a2 = compose1(mul_three, a1)     # ((x + 1)^2) * 3
    >>> a2(4)
    75
    >>> a2(5)
    108
    """
    return lambda x: f(g(x))

def composite_identity(f, g):
    """
    Возвращает функцию одного аргумента, которая возвращает True,
    если f(g(x)) равно g(f(x)). Можешь считать, что результат g(x)
    может быть аргументом f, и наоборот.

    >>> add_one = lambda x: x + 1        # прибавляет единицу к x
    >>> square = lambda x: x**2
    >>> b1 = composite_identity(square, add_one)
    >>> b1(0)                            # (0 + 1)^2 == 0^2 + 1
    True
    >>> b1(4)                            # (4 + 1)^2 != 4^2 + 1
    False
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Уже надоело напоминать.

$ git add .
$ git commit -m "Решение вопроса 5"

Вопрос 6: Считать-непересчитать

Рассмотри две функции count_factors и count_primes.

def count_factors(n):
    """Возвращает количество положительных целых делителей n."""
    i, count = 1, 0
    while i <= n:
        if n % i == 0:
            count += 1
        i += 1
    return count

def count_primes(n):
    """Возвращает число простых чисел, встречающихся до и включая n."""
    i, count = 1, 0
    while i <= n:
        if is_prime(i):
            count += 1
        i += 1
    return count

Код этих функций сильно похож. Нужно обобщить логику подсчёта и написать функцию count_cond, которая принимает функцию двух аргументов condition(n, i). Функция count_cond должна вернуть функцию одного аргумента n, которая подсчитывает количество целых чисел от 1 до n, которые удовлетворяют условию condition.

def count_cond(condition):
    """
    Возвращает функцию одного аргумента N, которая подсчитывает все числа от 1 до N,
    для которых выполняется предикат condition — функция двух аргументов.

    >>> count_factors = count_cond(lambda n, i: n % i == 0)
    >>> count_factors(2)   # 1, 2
    2
    >>> count_factors(4)   # 1, 2, 4
    3
    >>> count_factors(12)  # 1, 2, 3, 4, 6, 12
    6

    >>> is_prime = lambda n, i: count_factors(i) == 2
    >>> count_primes = count_cond(is_prime)
    >>> count_primes(2)    # 2
    1
    >>> count_primes(3)    # 2, 3
    2
    >>> count_primes(4)    # 2, 3
    2
    >>> count_primes(5)    # 2, 3, 5
    3
    >>> count_primes(20)   # 2, 3, 5, 7, 11, 13, 17, 19
    8
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вошло в привычку?

$ git add .
$ git commit -m "Решение вопроса 6"

Вопрос 7: Тебе же правда нравятся функции

Напиши функцию cycle, которая принимает три функции — f1, f2 и f3. Функция cycle должна вернуть другую функцию, которая принимает единственный аргумент n и возвращает ещё более другую функцию. Последняя должна принимать аргумент x и циклически применять к нему функции f1, f2 и f3 столько раз, каково значение n.

Вот что должно получаться:

  • n = 0 → возвращает x;

  • n = 1 → возвращает f1(x);

  • n = 2 → возвращает f2( f1(x) );

  • n = 3 → возвращает f3( f2( f1(x) ) );

  • n = 4 → (цикл начинается сначала), возвращает f1( f3( f2( f1(x) ) ) );

  • и так далее.

def cycle(f1, f2, f3):
    """Возвращает функцию, которая является функцией высшего порядка.

    >>> 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
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Не забудь отправить работу на проверку:

$ git add .
$ git commit -m "Полное решение"
$ git push