Домашнее задание № 5b

Для выполнения задания найди ссылку-приглашение в онлайн чате, получи доступ к репозиторию с файлом-заготовкой и заполни пропуски в нём. Фиксируй изменения (делай коммиты) после решения каждой задачи. Оставь название файла неизменным, иначе робот-проверятель не найдёт твои ответы. Отправить решения на GitHub нужно до истечения установленного срока.

Изменчивые функции

Вопрос 1

Определи функцию make_counter, которая возвращает функцию counter, которая принимает строку и возвращает количество вызовов этой функции с заданной строкой.

def make_counter():
    """Возвращает функцию counter.

    >>> c = make_counter()
    >>> c('a')
    1
    >>> c('a')
    2
    >>> c('b')
    1
    >>> c('a')
    3
    >>> c2 = make_counter()
    >>> c2('b')
    1
    >>> c2('b')
    2
    >>> c('b') + c2('b')
    5
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вопрос 2

Функция make_fib должна вернуть функцию, которая при первом вызове возвращает первое число Фибоначчи, при втором — второе и так далее.

Используй инструкцию nonlocal.
def make_fib():
    """Возвращает функцию, возвращающую следующее число Фибоначчи при каждом вызове.

    >>> fib = make_fib()
    >>> fib()
    0
    >>> fib()
    1
    >>> fib()
    1
    >>> fib()
    2
    >>> fib()
    3
    >>> fib2 = make_fib()
    >>> fib() + sum([fib2() for _ in range(5)])
    12
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вопрос 3

В лекции было показано, как с помощью функций создавать изменяющиеся объекты. Вот, например, функция make_withdraw, которая создает функцию, позволяющую снимать деньги со счёта.

def make_withdraw(balance):
    """Возвращает функцию withdraw с начальным балансом.
    >>> withdraw = make_withdraw(1000)
    >>> withdraw(100)
    900
    >>> withdraw(100)
    800
    >>> withdraw(900)
    'Недостаточно средств'
    """
    def withdraw(amount):
        nonlocal balance
        if amount > balance:
            return 'Недостаточно средств'
        balance = balance - amount
        return balance
    return withdraw

Напиши новую версию make_withdraw, возвращающую функцию для снятия денег со счета с парольной защитой. То есть make_withdraw теперь должна принимать также и пароль (строка) в дополнение к начальному балансу. Возвращаемая функция должна принимать два аргумента: сумму для снятия и пароль.

Защищенная паролем функция withdraw должна выполнять исключительно только те снятия, в которых указан правильный пароль. При получении неправильного пароля функция должна:

  1. Сохранить этот неправильный пароль в списке

  2. Вернуть строку 'Неправильный пароль'.

Если функция снятия была вызвана три раза с неверными паролями p1, p2 и p3, тогда она блокируется — то есть все последующие вызовы должны выводить:

"Твой аккаунт заблокирован. Попытки входа: ['p1', 'p2', 'p3']"

Неправильные пароли могут быть одинаковыми или отличаться:

def make_withdraw(balance, password):
    """Возвращает защищённую паролем функцию withdraw.

    >>> w = make_withdraw(100, 'hax0r')
    >>> w(25, 'hax0r')
    75
    >>> error = w(90, 'hax0r')
    >>> error
    'Недостаточно средств'
    >>> error = w(25, 'hwat')
    >>> error
    'Неверный пароль'
    >>> new_bal = w(25, 'hax0r')
    >>> new_bal
    50
    >>> w(75, 'a')
    'Неверный пароль'
    >>> w(10, 'hax0r')
    40
    >>> w(20, 'n00b')
    'Неверный пароль'
    >>> w(10, 'hax0r')
    "Твой аккаунт заблокирован. Попытки входа: ['hwat', 'a', 'n00b']"
    >>> w(10, 'l33t')
    "Твой аккаунт заблокирован. Попытки входа: ['hwat', 'a', 'n00b']"
    >>> type(w(10, 'l33t')) == str
    True
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Вопрос 4

Представь, что банковская система должна поддерживать объединённые счета. Определи функцию make_joint, которая принимает 3 аргумента:

  1. защищенную паролем функцию withdraw;

  2. пароль от функции withdraw;

  3. дополнительный пароль для доступа к счету.

Функция make_joint должна возвращать функцию withdraw, которая разрешает доступ к счету как со старым, так и с новым паролями. Обе функции списывают средства с одного счёта. При суммарном трёхкратном вводе неправильного пароля счёт должен заблокироваться.

Решение невелико (менее 10 строк кода) и не содержит строковых переменных. Идея в том, чтобы вызывать withdraw c правильным паролем и интерпретировать результат. Можно считать, что неудачные попытки (неправильные пароли, заблокированные счета, недостаток средств) возвращают строку, тогда как удачные вызовы возвращают числа.

Используй type(value) == str, чтобы проверить, что value является строкой.

def make_joint(withdraw, old_password, new_password):
    """Возвращает защищенную паролем функцию, которая присоединяется к существующей функции withdraw с новым паролем.

    >>> w = make_withdraw(100, 'hax0r')
    >>> w(25, 'hax0r')
    75
    >>> make_joint(w, 'my', 'secret')
    'Неверный пароль'
    >>> j = make_joint(w, 'hax0r', 'secret')
    >>> w(25, 'secret')
    'Неверный пароль'
    >>> j(25, 'secret')
    50
    >>> j(25, 'hax0r')
    25
    >>> j(100, 'secret')
    'Недостаточно средств'

    >>> j2 = make_joint(j, 'secret', 'code')
    >>> j2(5, 'code')
    20
    >>> j2(5, 'secret')
    15
    >>> j2(5, 'hax0r')
    10

    >>> j2(25, 'password')
    'Неверный пароль'
    >>> j2(5, 'secret')
    "Твой аккаунт заблокирован. Попытки входа: ['my', 'secret', 'password']"
    >>> j(5, 'secret')
    "Твой аккаунт заблокирован. Попытки входа: ['my', 'secret', 'password']"
    >>> w(5, 'hax0r')
    "Твой аккаунт заблокирован. Попытки входа: ['my', 'secret', 'password']"
    >>> make_joint(w, 'hax0r', 'hello')
    "Твой аккаунт заблокирован. Попытки входа: ['my', 'secret', 'password']"
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"

Генераторы

Вопрос 5

Определи функцию generate_paths, которая принимает дерево t и значение x и возвращает генератор, возвращающий все возможные пути от корня t к узлу со значением x. Каждый путь должен быть представлен списком значений из соответствующих вершин. Возвращать пути можно в любом порядке.

def generate_paths(t, x):
    """Возвращает генератор всех возможных путей от корня t до значения x в виде списков.

    >>> t1 = tree(1, [tree(2, [tree(3), tree(4, [tree(6)]), tree(5)]), tree(5)])
    >>> print_tree(t1)
    1
      2
        3
        4
          6
        5
      5
    >>> next(generate_paths(t1, 6))
    [1, 2, 4, 6]
    >>> path_to_5 = generate_paths(t1, 5)
    >>> sorted(list(path_to_5))
    [[1, 2, 5], [1, 5]]

    >>> t2 = tree(0, [tree(2, [t1])])
    >>> print_tree(t2)
    0
      2
        1
          2
            3
            4
              6
            5
          5
    >>> path_to_2 = generate_paths(t2, 2)
    >>> sorted(list(path_to_2))
    [[0, 2], [0, 2, 1, 2]]
    """
    "*** ТВОЙ КОД ЗДЕСЬ ***"