Нормальные имена переменных

С этого момента мы учимся использовать нормальные имена переменных

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

Допускается использование односложных имен переменных для счётчиков (i, j, k) в циклах for, а также если это имя фигурирует в условии задачи (N, M, K, L).

В именах должны использоваться только английские слова. Транслитерации русских слов не допускаются.

Если имя переменной или функции состоит из нескольких слов, то они должны разделяться символом подчеркивания. Например:
calc_sqrt – божественно
calculate_square_root – допустимо, но длинновато
vychislenie_kornya – недопустимо (используются русские имена)
f – недопустимо (непонятно назначение функции)

Обычно в качестве имён функций и переменных используются одно или несколько слов (или их сокращений) маленькими буквами с разделением символом подчёркивания. Например, result, factorial, calc_factorial(n), calc_fact(n), iter, find_next_value(), num_apples, apple_num и т.д. Для перевода слов можно использовать яндекс.переводчик.

Ещё пачка примеров: total, tot_sum, cur_elem, iter, prev_elem, next_elem, item_sq, num_apples, amount, tot_amt, seq_len, str_len, average, fib1, fib2, result, floor_num, hall_num, area, numerator, denominator, denom, quot, ratio и т.д.

Имена констант записываются полностью заглавными буквами. Если имя константы состоит из нескольких слов, для их разделения используются подчеркивания. Например, EPSILON, MAX_SIZE.

Задачи с дурацкими именами переменных приниматься не будут.

Функции в языке Python

Ранее была задача вычисления числа сочетаний из n элементов по k, для чего необходимо вычисление факториалов трех величин: n, k и n-k. Для этого можно сделать три цикла, что приводит к увеличению размера программы за счет трехкратного повторения похожего кода. Вместо этого лучше сделать одну функцию, вычисляющую факториал любого данного числа n и трижды использовать эту функцию в своей программе. Соответствующая функция может выглядеть так:

def factorial(n):
    f = 1
    for i in range(2, n + 1):
        f *= i
    return f

Этот текст должен идти в начале программы, вернее, до того места, где мы захотим воспользоваться функцией factorial. Первая строчка этого примера является описанием нашей функции. factorial — идентификатор, то есть имя нашей функции. После идентификатора в круглых скобках идет список параметров, которые получает наша функция. Список состоит из перечисленных через запятую идентификаторов параметров. В нашем случае список состоит из одной величины n. В конце строки ставится двоеточие.

Далее идет тело функции, оформленное в виде блока, то есть с отступом. Внутри функции вычисляется значение факториала числа n и оно сохраняется в переменной f. Функция завершается инструкцией return f, которая завершает работу функции и возвращает значение переменной f. Инструкция return может встречаться в произвольном месте функции, ее исполнение завершает работу функции и возвращает указанное значение в место вызова. Если функция не возвращает значения, то инструкция return используется без возвращаемого значения, также в функциях, не возвращающих значения, инструкция return может отсутствовать.

Теперь мы можем использовать нашу функцию несколько раз. В этом примере мы трижды вызываем функцию factorial для вычисления трех факториалов: factorial(n), factorial(k), factorial(n-k).

n = int(input())
k = int(input())
print(factorial(n) // (factorial(k) * factorial(n - k)))

Мы также можем, например, объявить функцию binomial, которая принимает два целочисленных параметра n и k и вычисляет число сочетаний из n по k:

def binomial(n, k)
    return factorial(n) // (factorial(k) * factorial(n - k))

Тогда в нашей основной программе мы можем вызвать функцию binomial для нахождения числа сочетаний:

print(binomial(n, k))

Вернемся к задаче нахождения наибольшего из двух или трех чисел. Функцию нахождения максимума из двух чисел можно написать так:

def max(a, b):
    if a > b:
        return a
    else:
        return b

Теперь мы можем реализовать функцию max3, находящую максимум трех чисел:

def max3(a, b, c):
    return max(max(a, b), c)

Функция max3 дважды вызывает функцию max для двух чисел: сначала, чтобы найти максимум из a и b, потом чтобы найти максимум из этой величины и c.

Возвращаем из функции и принимаем снаружи наборы чисел

Если нужно, чтобы функция вернула не одно значение, а два или более, то для этого функция может вернуть кортеж из двух или нескольких значений:

return (a, b)

Тогда результат вызова функции тоже нужно присваивать кортежу:

(n, m) = f(a, b)

О тестировании

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

A: Среднее значение

Напишите функцию average(A), которая получает на вход список чисел и возвращает среднее значение элементов данного списка. Список содержит только числа и не пуст.

Сдайте на проверку только тело функции. Сдаваемый в ejudge код должен выглядеть так:

def average(A):
    решение
# Ничего после функции не добавлять! Ни input'ов, ни print'ов!
average([1, 7, 9])
5.666666666666667
IDE

B: Медиана трех чисел

Напишите функцию median(a, b, c), которая получает на вход три числа и возвращает их медиану. В решении нельзя использовать циклы.

Сдайте на проверку только тело функции.

median(1, 9, 7)
7

Код вызова функции для тестирования
print(median(int(input()), int(input()), int(input())))
IDE

Ретурнобулофобия

Ретурнобулофобия — боязнь начинающих программистов использовать логическое выражение в инструкции return при возвращении значения из булевой функции. Симптомы: использование следующей конструкции в программе

def f():
    ...
    if expression:
       return True
    else
       return False

Лечение: использование следующей конструкции:

def f():
    ...
    return expression

Другие примеры проявления этого недуга:

if expression:
   return False
else
   return True
#
if expression:
   var = True
else
   var = False

C: Принадлежит ли точка квадрату - 1

Даны два действительных числа \(x\) и \(y\). Проверьте, принадлежит ли точка с координатами \((x,y)\) заштрихованному квадрату (включая его границу). На рисунке сетка проведена с шагом 1.

Решение оформите в виде функции is_point_in_square(x, y), возвращающую True, если точка принадлежит квадрату и False, если не принадлежит.

Функция is_point_in_square не должна содержать инструкцию if и должна иметь такой вид:

def is_point_in_square(x, y):
    return ...

Сдайте на проверку только тело функции.

is_point_in_square(0, 0)
True
is_point_in_square(3, -7)
False

Код вызова функции для тестирования
res = is_point_in_square(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

D: Принадлежит ли точка квадрату - 2

Решите аналогичную задачу для такого квадрата:

Решение должно соответствовать требованиям для решения задачи C.

Функция должна называться is_point_in_rhombus.

is_point_in_rhombus(0, 0)
True
is_point_in_rhombus(1, 1)
False

Код вызова функции для тестирования
res = is_point_in_rhombus(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

E: Принадлежит ли точка кругу

Даны пять действительных чисел: \(x\), \(y\), \(x_c\), \(y_c\), \(r\). Проверьте, принадлежит ли точка \((x,y)\) кругу с центром \((x_c,y_c)\) и радиусом \(r\).

Решение оформите в виде функции is_point_in_circle(x, y, xc, yc, r).

Решение должно соответствовать требованиям для решения задачи C.

is_point_in_circle(0.5, 0.5, 0, 0, 1)
True
is_point_in_circle(0.5, 0.5, 1, 1, 0.1)
False

Код вызова функции для тестирования
res = is_point_in_circle(float(input()), float(input()), float(input()), float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

F: Принадлежит ли точка области

Проверьте, принадлежит ли точка данной закрашенной области. Область неограничена снизу.

Решение оформите в виде функции is_point_in_area(x, y).

Инструкцию if использовать по-прежнему нельзя.

is_point_in_area(-1, 2)
True
is_point_in_area(0, 0)
False

Код вызова функции для тестирования
res = is_point_in_area(float(input()), float(input()))
if type(res) != bool:
    print("Функция должна возвращать значение типа bool")
elif res:
    print("YES")
else:
    print("NO")
IDE

Локальные и глобальные переменные

Внутри функции можно использовать переменные, объявленные вне этой функции

def f():
    print(a)
a = 1
f()

Здесь переменной a присваивается значение 1, и функция f печатает это значение, несмотря на то, что выше функции f эта переменная не инициализируется. Но в момент вызова функции f переменной a уже присвоено значение, поэтому функция f может вывести его на экран.

Такие переменные (объявленные вне функции, но доступные внутри функции) называются глобальными.

Но если инициализировать какую-то переменную внутри функции, использовать эту переменную вне функции не удастся. Например:

def f():
    a = 1
f()
print(a)

Получим NameError: name 'a' is not defined. Такие переменные, объявленные внутри функции, называются локальными. Эти переменные становятся недоступными после выхода из функции.

Интересным получится результат, если попробовать изменить значение глобальной переменной внутри функции:

def f():
    a = 1
    print(a)
a = 0
f()
print(a)

Будут выведены числа 1 и 0. То есть несмотря на то, что значение переменной a изменилось внутри функции, то вне функции оно осталось прежним! Это сделано в целях «защиты» глобальных переменных от случайного изменения из функции (например, если функция будет вызвана из цикла по переменной i, а в этой функции будет использована переменная i также для организации цикла, то эти переменные должны быть различными). То есть если внутри функции модифицируется значение некоторой переменной, то переменная с таким именем становится локальной переменной, и ее модификация не приведет к изменению глобальной переменной с таким же именем.

Более формально: интерпретатор Питон считает переменную локальной, если внутри нее есть хотя бы одна инструкция, модифицирующая значение переменной (это может быть оператор =, += и т.д., или использование этой переменной в качестве параметра цикла for, то эта переменная считается локальной и не может быть использована до инициализации. При этом даже если инструкция, модицифицирующая переменную никогда не будет выполнена: интерпретатор это проверить не может, и переменная все равно считается локальной. Пример:

def f():
    print(a)
    if False:
        a = 0
a = 1
f()

Возникает ошибка: UnboundLocalError: local variable 'a' referenced before assignment. А именно, в функции f идентификатор a становится локальной переменной, т.к. в функции есть команда, модифицирующая переменную a, пусть даже никогда и не выполняющийся (но интерпретатор не может это отследить). Поэтому вывод переменной a приводит к обращению к неинициализированной локальной переменной.

Чтобы функция могла изменить значение глобальной переменной, необходимо объявить эту переменную внутри функции, как глобальную, при помощи ключевого слова global:

def f():
    global a
    a = 1
    print(a)
a = 0
f()
print(a)

В этом примере на экран будет выведено 1 1, так как переменная a объявлена, как глобальная, и ее изменение внутри функции приводит к тому, что и вне функции переменная будет доступна.

Тем не менее, лучше не изменять значения глобальных переменных внутри функции. Если функция должна поменять какую-то переменную, то как правило это лучше сделать, как значение, возвращаемое функцией.

nonlocal-переменные

Кроме глобальных переменных бывают nonlocal-переменные. Они очень похожи на глобальные. При ссылке переменную, которая не определена в текущей функции, будет использована "ближайшая" из локальных переменных в объемлющих функциях, либо в глобальной области видимости. Если же переменную пометить как nonlocal, то поиск переменной не будет производиться за пределами инструкций def ни в глобальной области видимости объемлющего модуля, ни во встроенной области видимости, даже если переменные с такими именами там существуют. Переменные, помеченные как nonlocal, можно менять внутри функции, при этом будет изменяться её значение в соответствующей объемлющей функции. В отличие от global декларация nonlocal не позволяет создать переменную во внешней области видимости.

def tester():
    state = 1
    print(state)
    def nested():
        state = 2
        print(state)
    nested()

    print(state)
tester()
>>> 1
>>> 2
>>> 1
def tester():
    state = 1
    print(state)
    def nested():
        nonlocal state
        state = 2
        print(state)
    nested()

    print(state)
tester()
>>> 1
>>> 2
>>> 2

G: Разверните число

Дано число, получите число, составленное из тех же цифр, записанных в обратном порядке.

Решение оформите в виде функции reverse(n), которая получает на вход значение типа int и возвращает значение типа int.

reverse(179)
971

Код вызова функции для тестирования
res = reverse(int(input()))
if type(res) == int:
    print(res)
else:
    print("Функция должна возвращать значение типа int")
IDE

H: Следующий палиндром

Для данного числа \(n\) определите следующее за ним число, запись которого является палиндромом.

Решение оформите в виде функции next_palindrome(n), которая получает на вход значение типа int и возвращает значение типа int.

next_palindrome(179)
181

Код вызова функции для тестирования
res = next_palindrome(int(input()))
if type(res) == int:
    print(res)
else:
    print("Функция должна возвращать значение типа int")
IDE

I: Минимальный делитель числа

Дано натуральное число \(n>1\). Найдите его наименьший делитель, отличный от 1.

Решение оформите в виде функции min_divisor(n). Алгоритм должен иметь сложность \(O(\sqrt{n})\).

Указание. Если у числа \(n\) нет делителя не превосходящего \(\sqrt{n}\), то число \(n\) — простое и ответом будет само число \(n\).

Сдайте на проверку только тело функции.

min_divisor(4)
2
min_divisor(5)
5

Код вызова функции для тестирования
for i in range(2, int(input())):
    d = min_divisor(i)
    print(i, d)
IDE

J: Проверка числа на простоту

Дано натуральное число \(n>1\). Проверьте, является ли оно простым.

Решение оформите в виде функции is_prime(n), которая возвращает True для простых чисел и False для составных чисел. Решение должно иметь сложность \(O(\sqrt{n})\).

Сдайте на проверку только тело функции.

is_prime(2)
True
is_prime(4)
False

Код вызова функции для тестирования
for i in range(2, int(input())):
    res = is_prime(i)
    if type(res) != bool:
        print(i, "Функция is_prime(" + str(i) + ") вернула значение не типа bool")
    elif res:
        print(i, "YES")
    else:
        print(i, "NO")
IDE

K: Разложение на множители

Дано натуральное число \(n>1\). Постройте его разложение на простые множители. Решение должно быть оформлено в виде функции factor(n), возвращающей список делителей числа в порядке неубывания. Каждый делитель должен быть включен в список с учетом его кратности.

Функция должна перебирать делители до \(\sqrt{n}\), при уменьшении числа \(n\) граница перебора также должна уменьшатся.

Использование функций sqrt, pow, операции ** для извлечения квадратного корня может быть причиной долгого работы программы. Используйте вместо них возведение в квадрат при помощи операции умножения.

factor(132)
[2, 2, 3, 11]
factor(2)
[2]

Код вызова функции для тестирования
for i in range(int(input()), int(input()) + 1):
    print(i, factor(i))
IDE

Рекурсия

                        Эпиграф:
                        def short_story():
                            print("У попа была собака, он ее любил.")
                            print("Она съела кусок мяса, он ее убил,")
                            print("В землю закопал и надпись написал:")
                            short_story()

Как мы видели выше, функция может вызывать другую функцию. Но функция также может вызывать и саму себя! Рассмотрим это на примере функции вычисления факториала. Хорошо известно, что \(0!=1\), \(1!=1\). А как вычислить величину \(n!\) для большого \(n\)? Если бы мы могли вычислить величину \((n-1)!\), то тогда мы легко вычислим \(n!\), поскольку \(n!=n(n-1)\)!. Но как вычислить \((n-1)!\)? Если бы мы вычислили \((n-2)!\), то мы сможем вычисли и \((n-1)!=(n-1)(n-2)!\). А как вычислить \((n-2)!\)? Если бы... В конце концов, мы дойдем до величины \(0!\), которая равна \(1\). Таким образом, для вычисления факториала мы можем использовать значение факториала для меньшего числа. Это можно сделать и в программе на Питоне:

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

Подобный прием (вызов функцией самой себя) называется рекурсией, а сама функция называется рекурсивной.

Рекурсивные функции являются мощным механизмом в программировании. К сожалению, они не всегда эффективны (об этом речь пойдет позже). Также часто использование рекурсии приводит к ошибкам, наиболее распространенная из таких ошибок — бесконечная рекурсия, когда цепочка вызовов функций никогда не завершается и продолжается, пока не кончится свободная память в компьютере. Пример бесконечной рекурсии приведен в эпиграфе к этому разделу. Две наиболее распространенные причины для бесконечной рекурсии:

  1. Неправильное оформление выхода из рекурсии. Например, если мы в программе вычисления факториала забудем поставить проверку if n == 0, то factorial(0) вызовет factorial(-1), тот вызовет factorial(-2) и т.д.
  2. Рекурсивный вызов с неправильными параметрами. Например, если функция factorial(n) будет вызывать factorial(n), то также получиться бесконечная цепочка.

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

L: Возведение в степень

Дано действительное положительное число \(a\) и целое неотрицательное число \(n\). Вычислите \(a^n\) не используя циклы, стандартную функцию pow, операцию **, а используя рекуррентное соотношение \(a^n=a\cdot a^{n-1}\).

Решение оформите в виде функции power(a, n).

Сдайте на проверку только тело функции.

power(2, 3)
8

Код вызова функции для тестирования
print(power(float(input()), int(input())))
IDE

M: Числа Фибоначчи

Последовательность Фибоначчи определяется так: \[ \begin{cases} \varphi_0=0, \\ \varphi_1=1, \\ \varphi_{n}=\varphi_{n-1}+\varphi_{n-2} \text{, при } n \gt 1. \end{cases} \]

Напишите функцию fib(n), которая по данному целому неотрицательному n возвращает \(n\)-e число Фибоначчи.

Сдайте на проверку только тело функции.

fib(6)
8

Код вызова функции для тестирования
print(fib(int(input())))
IDE

Всегда ли полезна ли рекурсия?

Запустите программы, вычисляющие числа Фибоначчи при помощи рекурсивного и нерекурсивного алгоритма. Сравните время их работы для n=10, 30, 50, 70, 90, 110. Результат экспериментов пришлите в комментариях к задаче 13.

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

from time import time

def factorial(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

def factorial_with_recursion(n):
    if n == 0:
        return 1
    else:
        return n * factorial_with_recursion(n - 1)

REPEATS = 1000
TEST_NUM = 500

start_time = time()
for i in range(REPEATS):
    x = factorial(TEST_NUM)
finish_time = time()
print((finish_time - start_time) / REPEATS)

start_time = time()
for i in range(REPEATS):
    x = factorial_with_recursion(TEST_NUM)
finish_time = time()
print((finish_time - start_time) / REPEATS)

N: Рекурсия вместо цикла

Напишите рекурсивную функцию print_numbers(n), вызов которой приводит к печати на экран натуральных чисел от 1 до \(n\) по одному числу в строке. Циклы и генераторы списков использовать нельзя.

Подсказка. Число \(n\) является параметром. Как свести задачу “вывести числа от 1 до \(n\)” к задаче с меньшим значением параметра?

Сдайте на проверку только тело функции.

print_numbers(3)
1 2 3

Код вызова функции для тестирования
res = print_numbers(int(input()))
if res is not None:
    print("Функция не должна возвращать значения")
IDE

O: Произведение списка

Напишите функцию product(a), которая по данному списку чисел возвращает произведение элементов данного списка, не используя циклы.

Список содержит только числа, но может быть пустым.

Сдайте на проверку только тело функции.

product([1, 7, 9])
63

Код вызова функции для тестирования
print(product(list(map(int, input().split()))))
IDE

P: Разворот последовательности

Дана последовательность целых чисел, заканчивающаяся числом 0. Выведите эту последовательность в обратном порядке. Сделайте это без использования списков и циклов.

Решение оформите в виде функцииreverse(), не принимающей параметров, которая считывает со стандартного ввода последовательность чисел до появления числа 0 и выводит эту же последовательность в обратном порядке. Функция должна считать одно число и, если это число не 0, вызвать себя рекурсивно.

Сдайте на проверку только тело функции.

1 2 3 0
0 3 2 1

Код вызова функции для тестирования
res = reverse()
if res is not None:
    print("Функция не должна возвращать значения")
IDE

Q: Быстрое возведение в степень

Возводить в степень можно гораздо быстрее, чем за \(n\) умножений! Для этого нужно воспользоваться следующими рекуррентными соотношениями:

\(a^n=(a^2)^{n/2}\) при четном \(n\),

\(a^n=a\cdot a^{n-1}\) при нечетном \(n\).

Реализуйте алгоритм быстрого возведения в степень. Если вы все сделаете правильно, то сложность вашего алгоритма будет \(O(\log n)\).

Решение оформите в виде функции power(a, n). Использовать стандартную функцию pow и оператор ** по-прежнему запрещается.

Сдайте на проверку только тело функции.

power(1.000000001, 1000000000)
2.718282030814511

Код вызова функции для тестирования
print(power(float(input()), int(input())))
IDE

Распаковка

Для того, чтобы сделать использование функций ещё удобнее, в Python есть оператор * — распаковка. Его можно применять только применительно к параметрам вызываемой функции. При этом происходит следующее: из объекта, к которому применяется распаковка, извлекаются отдельные элементы и передаются в качестве отдельных параметров: Рассмотрим пример:
def func1():
    return 2, 5, 7

def func2(x, y, z):
    print(x + y + z)

func2(*func1())
Здесь функция func1() возвращает кортеж из трёх чисел, а функция func2() принимает на вход три элемента. Результат работы первой функции можно передать сразу во вторую, если распаковать результат её работы.

Другой пример использования распаковки --- это передача одного и того же набора параметров в разные функции:

def func1(x, y):
    print(x + y)

def func2(x, y):
    print((x ** 2 + y ** 2) ** 0.5)

pars = (3, 7)
func1(*pars)
func2(*pars)

Очень удобно использовать распаковку — внутри функции print:

def fibonacci(n):
    fib1, fib2 = 0, 1
    for i in range(n):
        fib1, fib2 = fib2, fib1 + fib2
        yield fib1
print(*fibonacci(10))
Таким образом, можно быстро выводить содержимое списков, результаты работы генераторов или функций, возвращающих кортежи или списки.

Также при помощи распаковки можно передавать числовые параметры из стандартного ввода в функцию в одну строчку:

def max(a, b, c):
    return max(max(a, b), c)
print(max(*map(int, input().split())))

Необязательные и именованные переменные

Переменные у функции могут быть необязательными. Для того, чтобы сделать переменную необязательной, после её декларации нужно указать её значение по умолчанию
def add5(a, b=5):
    return a + b

>>> add5(3)
8
>>> add5(3, 10)
13

При вызове функции можно не соблюдать порядок переменных, если указывать их имена при вызове:

def foo(fst, snd):
    return fst, snd
>>> foo(1,2)
(1, 2)
>>> foo(snd=2, fst=1)
(1, 2)

Кроме того, можно указать, что все переменные начиная с некоторой могут быть переданы только по имени:

def foo(fst, *, snd=None):
    return fst, snd
>>> foo(1)
(1, None)
>>> foo(1,2)
Traceback (most recent call last):
  File "< input >", line 1, in
TypeError: foo() takes 1 positional argument but 2 were given
>>> foo(1, snd=2)
(1, 2)

R: Алгоритм Евклида

Для быстрого вычисления наибольшего общего делителя двух чисел используют алгоритм Евклида. Он построен на следующем соотношении: \(НОД(a,b)=НОД(a\bmod b,b)\).

Реализуйте рекурсивный алгоритм Евклида в виде функции gcd(a, b), возвращающей искомое значение.

Сдайте на проверку только тело функции.

gcd(12, 16)
4

Код вызова функции для тестирования
print(gcd(int(input()), int(input())))
IDE

S: Ханойские башни

Головоломка “Ханойские башни” состоит из трех стержней, пронумерованных числами 1, 2, 3. На стержень 1 надета пирамидка из \(n\) дисков различного диаметра в порядке возрастания диаметра. Диски можно перекладывать с одного стержня на другой по одному, при этом диск нельзя класть на диск меньшего диаметра. Необходимо переложить всю пирамидку со стержня 1 на стержень 3 за минимальное число перекладываний.

Напишите функцию, которая решает головоломку; для данного числа дисков n печатает последовательность перекладываний в формате a b c, где a — номер перекладываемого диска, b — номер стержня с которого снимается данный диск, c — номер стержня на который надевается данный диск.

Например, строка 1 2 3 означает перемещение диска номер 1 со стержня 2 на стержень 3. В одной строке печатается одна команда. Диски пронумерованы числами от 1 до n в порядке возрастания диаметров.

Программа должна вывести минимальный (по количеству произведенных операций) способ перекладывания пирамидки из данного числа дисков.

Указание: подумайте, как переложить пирамидку из одного диска? Из двух дисков? Из трех дисков? Из четырех дисков? Пусть мы научились перекладывать пирамидку из \(n\) дисков с произвольного стержня на любой другой, как переложить пирамидку из \(n+1\) диска, если можно пользоваться решением для \(n\) дисков.

Решение оформите в виде функции move(n, start, finish), которая печатает последовательность перекладываний дисков для перемещения пирамидки высоты n со стержня номер start на стержень номер finish.

Сдайте на проверку только тело функции.

В примере ниже пирамидка из 2 дисков перекладывается со стержня 1 на стержень 3.

move(2, 1, 3)
1 1 2 2 1 3 1 2 3

Код вызова функции для тестирования
move(int(input()), int(input()), int(input()))
IDE

T: Ремонт в Ханое

Постановлением ЮНЕСКО оригинал Ханойской башни был подвергнут реставрации. В связи с этим во время пользования головоломкой нельзя было перекладывать кольца с первого стержня сразу на третий и наоборот.

Решите головоломку с учетом этих ограничений. Вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000, при условии, что количество дисков не превосходит 10.

В этой и последующих задачах на проверку необходимо сдать программу полностью.

Тесты к этой задаче закрытые.

1
1 1 2 1 2 3
2
1 1 2 1 2 3 2 1 2 1 3 2 1 2 1 2 2 3 1 1 2 1 2 3
IDE

U★: Циклические башни

На дорогах Ханоя было введено одностороннее круговое движение, поэтому теперь диск со стержня 1 можно перекладывать только на стержень 2, со стержня 2 на 3, а со стержня 3 на 1.

Решите головоломку с учетом этих ограничений. Вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000, при условии, что количество дисков не превосходит 10.

1
1 1 2 1 2 3
2
1 1 2 1 2 3 2 1 2 1 3 1 2 2 3 1 1 2 1 2 3
IDE

V★: Несправедливые башни

В Ханое несправедливо запретили класть самый маленький диск (номер 1) на средний колышек (номер 2).

Решите головоломку с учетом этих ограничений. Вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000, при условии, что количество дисков не превосходит 10.

2
1 1 3 2 1 2 1 3 1 2 2 3 1 1 3
IDE

W★: Сортирующие башни

Первоначально все диски лежат на стержне номер 1. Переместите диски с нечетными номерами на стержень номер 2, а с четными номерами - на стержень номер 3.

Вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000, при условии, что количество дисков не превосходит 10.

2
1 1 2 2 1 3
3
1 1 2 2 1 3 1 2 3 3 1 2 1 3 2
IDE

X★: Обменные башни

Как и в предыдущих задачах, дано три стержня, на первом из которых надето \(n\) дисков различного размера. Необходимо их переместить на стержень 3 по следующим правилам:

Самый маленький диск (номер 1) можно в любой момент переложить на любой стержень. Перемещение диска номер 1 со стержня a на стержень b будем обозначать 1 a b.

Можно поменять два диска, лежащих на вершине двух стержней, если размеры этих дисков отличаются на 1. Например, если на вершине стержня с номером a лежит диск размером 5, а на вершине стержня с номером b лежит диск размером 4, то эти диски можно поменять местами. Такой обмен двух дисков будем обозначать 0 a b (указываются номера стержней, верхние диски которых обмениваются местами).

Для данного числа дисков \(n\), не превосходящего 10, найдите решение головоломки. вам не нужно находить минимальное решение, но количество совершенных перемещений не должно быть больше 200000.

1
1 1 3
2
1 1 3 0 1 3 1 1 3
IDE

Y: Фишки

Дана полоска из клеток, пронумерованных от 1 до N слева направо. Разрешено снимать или ставить фишку на клетку с номером 1 или на клетку, следующую за самой левой из установленных фишек. Изначально полоска пуста. Нужно разместить фишки во всех клетках.

Программа получает на вход количество клеток в полоске \(N\) \((1\le N\le 10)\).

Программа должна вывести последовательность номеров клеток, с которыми совершается действие. Если фишка снимается, то номер клетки должен выводиться со знаком минус. Количество действий не должно превышать \(10^4\). Если существует несколько возможных решений задачи, то разрешается вывести любое.

Тесты к этой задаче закрытые.

3
1 2 -1 3 1
IDE

Z: Небоскреб

В небоскребе \(n\) этажей. Известно, что если уронить стеклянный шарик с этажа номер \(p\), и шарик разобьется, то если уронить шарик с этажа номер \(p+1\), то он тоже разобьется. Также известно, что при броске с последнего этажа шарик всегда разбивается.

Вы хотите определить минимальный номер этажа, при падении с которого шарик разбивается. Для проведения экспериментов у вас есть два шарика. Вы можете разбить их все, но в результате вы должны абсолютно точно определить этот номер.

Определите, какого числа бросков достаточно, чтобы заведомо решить эту задачу.

Программа получает на вход количество этажей в небоскребе \(n\) и выводит наименьшее число бросков, при котором можно всегда решить задачу.

Тесты к этой задаче закрытые.

4
2
20
6

Комментарий к первому примеру. Нужно бросить шарик со 2-го этажа. Если он разобьется, то бросим второй шарик с 1-го этажа, а если не разобьется - то бросим шарик с 3-го этажа.

Подсказки.
1. Как следует действовать, если шарик был бы только один?
2. Пусть шариков два и мы бросили один шарик с этажа номер \(k\). Как мы будем действовать в зависимости от того, разобьется ли шарик или нет?
3. Пусть f(n) - это минимальное число бросков, за которое можно определить искомый этаж, если бы в небоскребе было n этажей. Выразите f(n) через значения f(a) для меньших значений a.

IDE

ZA: Построение кривой дракона

Рассмотрим кривую дракона на шаге номер \(n\ge 2\). Вы движетесь из точки (0, 0) и выписываете направления поворотов после прохождения каждого отрезка. Поворот направо обозначается буквой R, поворот налево — буквой L. Выведите последовательность поворотов.

Программа получает на вход число \(n\ge 2\) и должна вывести последовательность из букв L и R в одной строке без пробелов.

3
RRL
4
RRLRRLL
IDE

ZB★: Небоскрёб-2

Аналогично условию первой задачи, в здании \(n\ge2\) этажей, а у вас есть \(k\ge 1\) шариков. Определите минимальное количество бросков, при помощи которого можно определить минимальный номер этажа, при броске с которого шарик разбивается. При броске с этажа \(n\) шарик всегда разбивается.

Программа получает на вход два числа \(n\) и \(k\) и должна вывести одно число — необходимое число бросков.

4 2
2
20 2
6
IDE

ZC★: Небоскрёб-3

Теперь в небоскрёбе \(n\) этажей, и вам нужно опять-таки определить номер минимального этажа, при броске с которого шарик разбивается, при броске с этажа \(n\) шарик всегда разбивается. Вы успеете сделать не более \(t\) бросков, \(n\le 2^t\). Определите, какое минимальное количество шариков вам нужно иметь для того, чтобы решить эту задачу не более, чем за \(t\) бросков.

20 6
2
IDE

Лямбда-функции

В тех случаях, когда у функции нет необязательных параметров, а её тело может быть записано в одну строчку, существует другой способ записи функции:
foo = lambda ([параметры]): ([выражение])
# Синоним
def foo([параметры]):
    return [выражение]
Однако приведённый пример является анти-паттерном. Лямбда функции предназначены для использования там, где функция передаётся в качестве параметра и более нигде не используется.

Вообще, синтаксис лямбд достаточно полон, чтобы в одну строчку можно было реализовать вообще любую программу. Вместе с тернаным оператором условия лямбды позволяют делать совершенно нечитаемые работающие однострочники.

# Числа Фибоначчи
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:f(x,f), range(20))))
>>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

# Факториал
print((lambda n,f:f(n,1,f))(20,(lambda n,m,f:m if n<=1 else f(n-1,m*n,f))))
>>> 2432902008176640000

# В некоторых случаях даже функции не нужны. Первые простые числа
print([x for x in range(2, 101) if all(x % i for i in range(2, int(x**0.5)+1))])
>>> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

Замыкание

Одно из интересных понятий функционального программирования — это замыкания (closure).
В питоне не слишком часто используют замыкания явно, в то время, как, скажем, в javascript без замыканий обойтись практически невозможно.
Смысл замыкания состоит в том, что определение функции "замораживает" окружающий её контекст на момент определения. Это может делаться различными способами, например, за счёт параметризации создания функции. При замыкании будут использованы переменные из объемлющих функций. Для того, чтобы переменная попала в замыкание, она должна быть упомянута в коде функции:
def print_msg(msg):
    def fun():
        print(msg)
    return fun  # Помните, что в питоне всё - это объект. Функция тоже, её можно вернуть и воспользоваться потом.

printer = print_msg("Hello")
printer()
>>>Hello
def multiplier( n ):  # multiplier возвращает функцию умножения на n
    def mul( k ):
        return n * k
    return mul

mul3 = multiplier(3)  # mul3 - функция, умножающая на 3
print(mul3(3), mul3(5))
>>> 9 15

Правильно захваченные переменные при этом можно даже успешно модифицировать:

def tester(start):
    state = start  # В каждом вызове сохраняется свое значение state
    def nested(label):
        nonlocal state      # Объект state находится
        print(label, state) # в объемлющей области видимости
        state += 1 # Изменит значение переменной, объявленной как nonlocal
    return nested

>>> F = tester(0)
>>> F('spam')          # Будет увеличивать значение state при каждом вызове
spam 0
>>> F('ham')
ham 1
>>> F('eggs')
eggs 2

Но вообще тема эта достаточно сложная. В результате неаккуратного использования может быть отстрелено произвольное количество конечностей. Например, нужно иметь в виду, что в замыкание попадает имя переменной, а не её значение.

def gen_adders():
    i = 5
    def add5(x):
        return x + i
    i = 10
    def add10(x):
        return x + i
    i = 15
    return add5, add10

adder1, adder2 = gen_adders()
print(adder1(0))  # -> 15
print(adder2(0))  # -> 15
В данном случае обе функции используют одну и ту же захваченную из функции gen_adders переменную i, что и приводит к такому «неожиданному» поведению.