Сложные задачи

Сложные задачи - предназначены для проработки самостоятельного анализа задачи и поиска решения. В некоторых задачах требуется использование библиотек
ОСНОВЫ
Числа Фибоначчи
Реализуйте функцию fib, находящую положительные Числа Фибоначчи. Аргументом функции является порядковый номер числа.

Числа Фибоначчи - последовательность в которой первые два числа равны либо 1 и 1, либо 0 и 1, а каждое последующее число равно сумме двух предыдущих чисел.

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, …
Сумма двоичных чисел
Реализуйте функцию binary_sum, которая принимает на вход два двоичных числа (в виде строк) и возвращает их сумму. Результат (вычисленная сумма) также должен быть бинарным числом в виде строки.

Посмотрите примеры работы функции:
binary_sum('10', '1') # 11
binary_sum('1101', '101') # 10010

Физзбазз
Реализуйте функцию fizz_buzz, которая возвращает строку с числами (через пробел) в диапазоне от begin до end включительно. При этом:
Если число делится без остатка на 3, то вместо него выводится слово Fizz
Если число делится без остатка на 5, то вместо него выводится слово Buzz
Если число делится без остатка и на 3, и на 5, то вместо числа выводится слово FizzBuzz
В остальных случаях в строку добавляется само число

Функция принимает два параметра (begin и end), определяющих начало и конец диапазона (включительно). Если диапазон пуст (в случае, когда begin > end), то функция возвращает пустую строку.

Пример
Вызов функции:
>>> from solution import fizz_buzz
>>>
>>> print(fizz_buzz(1, 5))
1 2 Fizz 4 Buzz
>>>
>>> print(fizz_buzz(11, 20))
11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
>>>
Палиндром
Палиндром — слово или текст, одинаково читающиеся в обоих направлениях. Примеры:

"я",
"радар",
"асса",
"ишак ищет у тещи каши"

Реализуйте функцию is_palindrome, которая принимает на вход слово, определяет является ли оно палиндромом и возвращает логическое значение.

Примеры использования:
>>> from solution import is_palindrome
>>> is_palindrome('') # пустая строка тоже считается палиндромом
True
>>> is_palindrome('radar')
True
>>> is_palindrome('a')
True
>>> is_palindrome('abs')
False
>>> is_palindrome('ишак ищет у тещи каши')
True
>>>
Классификация отрезков
В рамках этого испытания вы реализуете небольшой набор функций, работающих с отрезками прямых на двухмерной плоскости. Отрезок в нашем случае будет кодироваться в виде пары пар и выглядеть как-то так: ((x1, y1), (x2, y2)) (вложенные пары — это концы отрезка).

Вам нужно реализовать четыре функции:
is_degenerated должна возвращать истину, если отрезок вырожден в точку (начало и конец совпадают);
is_vertical должна возвращать "истину", если отрезок — вертикальный;
is_horizontal должна возвращать "истину", если отрезок — горизонтальный;
is_inclined должна возвращать "истину", если отрезок — наклонный (не вертикальный и не горизонтальный).

Использование этих функций может выглядеть так:
>>> line1 = (0, 10), (100, 130)
>>> is_vertical(line1)
False
>>> is_horizontal(line1)
False
>>> is_degenerated(line1)
False
>>> is_inclined(line1)
True
>>> line2 = (42, 1), (42, 2)
>>> is_vertical(line2)
True
>>> line3 = (100, 50), (200, 50)
>>> is_horizontal(line3)
True
Вращение троек
В этом испытании вы будете работать с "тройками" — кортежами из трёх элементов. Вам предстоит реализовать две функции, которые "вращают" тройку влево и вправо.

Как это выглядит, вы можете понять из пары примеров:
>>> triple = ('A', 'B', 'C')
>>> rotate_left(triple)
('B', 'C', 'A')
>>> rotate_right(triple)
('C', 'A', 'B')
Разница углов
Напишите функцию diff, которая принимает два угла (int) со значениями в диапазоне от 0 до 360, и возвращает наименьшую разницу между ними.

Примеры:
>>> diff(0, 45)
45
>>> diff(0, 180)
180
>>> diff(0, 190) # не 190, а 170, потому что 170 меньше
170
>>> diff(120, 280)
160

Подсказки
Вам могут пригодиться функция abs, возвращающая абсолютное значение (модуль) аргумента, и функция min, выбирающая из двух аргументов минимальный:
>>> abs(-1)
1
>>> min(10, 7)
7
Степени тройки
Реализуйте функцию is_power_of_three, которая определяет, является ли переданное число натуральной степенью тройки. Например, число 27 — это третья степень: 3 ** 3, а 81 — это четвёртая: 3 ** 4.

Пример:
>>> is_power_of_three(1)
True
>>> is_power_of_three(2)
False
>>> is_power_of_three(9)
True
СПИСКИ
Улитка
Матрицу можно представить в виде двумерного списка.

Например, список [[1, 2, 3, 4], [5, 6, 7, 8]] — это отображение матрицы:
1 2 3 4
5 6 7 8


Реализуйте функцию snail_path, которая принимает на вход матрицу и возвращает список элементов матрицы по порядку следования от левого верхнего элемента по часовой стрелке к внутреннему. Движение по матрице напоминает улитку:
Умножение матриц
Операция умножения двух матриц А и В представляет собой вычисление результирующей матрицы С, где каждый элемент C(ij) равен сумме произведений элементов в соответствующей строке первой матрицы A(ik) и элементов в столбце второй матрицы B(kj).

Две матрицы можно перемножать только в том случае, если количество столбцов в первой матрице совпадает с количеством строк во второй матрице. Это значит, что первая матрица обязательно должна быть согласованной со второй матрицей. В результате операции умножения матрицы размера M×N на матрицу размером N×K является матрица размером M×K.

Реализуйте функцию multiply, которая принимает две матрицы и возвращает новую матрицу — результат их произведения.

Примеры
>>> from solution import multiply
>>> A = [[1, 2], [3, 2]]
>>> B = [[3, 2], [1, 1]]
>>> multiply(A, B)
[[5, 4], [11, 8]]
>>>
>>> C = [
... [2, 5],
... [6, 7],
... [1, 8],
... ]
>>> D = [
... [1, 2, 1],
... [0, 1, 0],
... ]
>>> multiply(C, D)
[[2, 9, 2], [6, 19, 6], [1, 10, 1]]
>>>
Увеличение двумерного списка
Реализуйте функцию enlarge, которая принимает изображение в виде двумерного списка строк и увеличивает его в два раза, то есть удваивает каждый символ по горизонтали и вертикали.

>>> def show(image):
... for line in image:
... print(line)
...
>>> dot = ['@']
>>> show(enlarge(dot))
@@
@@
>>> frame = [
... '****',
... '* *',
... '* *',
... '****'
... ]
>>> show(frame)
****
* *
* *
****
>>> show(enlarge(frame))
********
********
** **
** **
** **
** **
********
********
Подсказка
Если вам потребуется склеить список строк в одну строку, воспользуйтесь таким методом:
>>> chunks = ["Hello", " ", "World", "!"]
>>> ''.join(chunks)
'Hello World!'
Возрастающая последовательность
Реализуйте функцию is_continuous_sequence, которая проверяет, является ли переданная последовательность целых чисел возрастающей непрерывно (не имеющей пропусков чисел). Например, последовательность [4, 5, 6, 7] — непрерывная, а [0, 1, 3] — нет. Последовательность может начинаться с любого числа. Главное условие — отсутствие пропусков чисел. Последовательность из одного числа не может считаться возрастающей.

Примеры
>>> is_continuous_sequence([10, 11, 12, 13])
True
>>> is_continuous_sequence([-5, -4, -3])
True
>>> is_continuous_sequence([10, 11, 12, 14, 15])
False
>>> is_continuous_sequence([1, 2, 2, 3])
False
>>> is_continuous_sequence([7])
False
>>> is_continuous_sequence([])
False
Зеркалирование матрицы
Реализуйте функцию mirror_matrix, которая принимает двумерный список (матрицу) и изменяет его (по месту) таким образом, что правая половина матрицы становится зеркальной копией левой половины, симметричной относительно вертикальной оси матрицы. Для простоты условимся, что матрица всегда имеет чётное количество столбцов и количество столбцов всегда равно количеству строк.

Пример
>>> from solution import mirror_matrix
>>> l = [
... [11, 12, 13, 14, 15, 16],
... [21, 22, 23, 24, 25, 26],
... [31, 32, 33, 34, 35, 36],
... [41, 42, 43, 44, 45, 46],
... [51, 52, 53, 54, 55, 56],
... [61, 62, 63, 64, 65, 66],
... ]
...
>>> mirror_matrix(l)
>>> l == [
... [11, 12, 13, 13, 12, 11],
... [21, 22, 23, 23, 22, 21],
... [31, 32, 33, 33, 32, 31],
... [41, 42, 43, 43, 42, 41],
... [51, 52, 53, 53, 52, 51],
... [61, 62, 63, 63, 62, 61],
... ]
...
True
>>>
Сумма интервалов
Реализуйте функцию sum_of_intervals, которая принимает на вход список интервалов и возвращает сумму всех длин интервалов. В данной задаче используются только интервалы целых чисел от 1 до ∞ , которые представлены в виде списков. Первое значение интервала всегда будет меньше, чем второе значение. Например, длина интервала [1, 5] равна 4, а длина интервала [5, 5] равна 0. Пересекающиеся интервалы должны учитываться только один раз.

Примеры
>>> from solution import sum_of_intervals
>>> sum_of_intervals([
... [1, 1],
... ])
0
>>> sum_of_intervals([
... [1, 2],
... [50, 100],
... [60, 70],
... ])
51
>>> sum_of_intervals([
... [1, 2],
... [5, 10],
... ])
6
Треугольник Паскаля
Треугольник Паскаля — бесконечная таблица биномиальных коэффициентов, имеющая треугольную форму. В этом треугольнике на вершине и по бокам стоят единицы. Каждое число равно сумме двух расположенных над ним чисел. Строки треугольника симметричны относительно вертикальной оси.
0: 1
1: 1 1
2: 1 2 1
3: 1 3 3 1
4: 1 4 6 4 1
5:1 5 10 10 5 1

Напишите функцию triangle, которая возвращает указанную строку треугольника паскаля в виде массива.
Пример:
>>> triangle(0)
[1]
>>> triangle(4)
[1, 4, 6, 4, 1]
Обратная польская запись
В данном упражнении необходимо реализовать стековую машину, то есть алгоритм, проводящий вычисления по обратной польской записи.

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

Например, выражение (1 + 2) * 4 + 3 в постфиксной нотации будет выглядеть так: 1 2 + 4 * 3 +, а результат вычисления: 15. Другой пример - выражение: 7 - 2 * 3, в постфиксной нотации: 7 2 3 * -, результат: 1.

Реализуйте функцию rpn_calc, которая принимает список, каждый элемент которого содержит число или знак операции (+, -, *, /). Функция должна вернуть результат вычисления по обратной польской записи.

Примеры
>>> rpn_calc([1, 2, '+', 4, '*', 3, '+'])
15
>>> rpn_calc([7, 2, 3, '*', '-'])
1
>>>
Список диапазонов
Реализуйте функцию summary_ranges, которая находит в списке непрерывные возрастающие последовательности чисел и возвращает список с их перечислением.

Примеры
>>> summary_ranges([])
[]
>>> summary_ranges([1])
[]
>>> summary_ranges([1, 2, 3])
['1->3']
>>> summary_ranges([0, 1, 2, 4, 5, 7])
['0->2', '4->5']
>>> summary_ranges([110, 111, 112, 111, -5, -4, -2, -3, -4, -5])
['110->112', '-5->-4']
Длина последнего слова
Реализуйте функцию length_of_last_word, которая возвращает длину последнего слова переданной на вход строки. Словом считается любая последовательность, не содержащая пробелов.

>>>length_of_last_word('')
0
>>>length_of_last_word('man in Black')
5
>>>length_of_last_word('hello, world! ')
6
Чанкование потока
В испытании "Чанкование" вам нужно было реализовать функцию, которая "нарезает" входную последовательность (любой iterable) на куски заданной длины. В этом же испытании вам нужно будет проделать нечто подобное, но уже с итератором — потенциально бесконечным! Иначе говоря, вам предстоит обрабатывать поток данных. Примерами таких потоков могут быть читаемый с диска файл очень большого размера или данные видео-трансляции, передаваемые по сети. В обоих случаях вы не можете себе позволить получить все данные сразу в виде структуры в памяти — вам её просто не хватит. И поэтому же вы не можете накапливать список кусочков внутри вашей функции, вам нужно возвращать поток кусочков.

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

Внимание, в этот раз вам нужно будет формировать куски строго заданной длины! Если для последнего куска (если поток вообще закончится) не хватит элементов, то весь кусок отбрасывается!

Примеры применения функции:
>>> list(ichunks(2, [1, 2, 3, 4, 5]))
[[1, 2], [3, 4]]
>>> # ^ пятёрка была отброшена
>>>
>>> import itertools
>>> # itertools.count() - бесконечный поток чисел 1, 2, 3...
>>> list(itertools.islice(itertools.count(), 10000, 10005))
[10000, 10001, 10002, 10003, 10004]
>>>
>>> stream = ichunks(3, itertools.count()) # поток троек чисел
>>> list(itertools.islice(stream, 10000, 10002))
[[30000, 30001, 30002], [30003, 30004, 30005]]

Возможно, вы отметили, что имя функции начинается с "i" и отрезает то, что будет содержать возвращаемый итератор. Такое имя выбрано неспроста: похожим образом нередко именуют функции, работающие с итераторами. Например, несколько функций из стандартного модуля itertools названы в этом стиле.

Замечания
У нас, увы, нет возможности проверить код на переполнение памяти. Поэтому мы полагаемся на вашу ответственность. Если вы вернёте что-то вроде iter(huge_list), тесты будут пройдены, но такое решение не будет по-настоящему правильным!

Подсказки
Чтобы вернуть итератор, воспользуйтесь при решении функциями, которые уже возвращают итераторы: map, zip, функции из модуля itertools.
Транспонирование матриц
Транспонированием матрицы называется операция, при которой столбцы матрицы становятся строками, а строки становятся столбцами.

Представим некую двумерную матрицу
1 2 3
4 5 6
7 8 9

После транспонирования матрица будет выглядеть так:
1 4 7
2 5 8
3 6 9

Транспонирование производилось по главной диагонали, то есть 1, 5 и 9 остались на своих местах, а сама матрица оказалась как бы повёрнута на 180 градусов относительно этой воображаемой диагональной оси.

Реализуйте функцию transposed, которая должна принимать матрицу в виде списка списков и возвращать транспонированную матрицу (новый список списков).

Имейте в виду, что хоть в математике и транспонируют строго квадратные матрицы, ваша функция transposed должна быть более "всеядной": она должна уметь переворачивать и прямоугольные матрицы!

Примеры
>>> transposed([[1]])
[[1]]
>>> transposed([[1, 2], [3, 4]])
[[1, 3], [2, 4]]
>>> transposed([[1, 2], [3, 4], [5, 6]])
[[1, 3, 5], [2, 4, 6]]
>>> transposed(transposed([[1, 2]])) == [[1, 2]]
True
Самая длинная подстрока
Реализуйте функцию find_longest_length, принимающую на вход строку и возвращающую длину максимальной последовательности из не повторяющихся символов. Подстрока может состоять из одного символа. Например в строке qweqrty, можно выделить следующие подстроки: qwe, weqrty.

Самой длинной будет weqrty, а её длина — 6 символов.
>>> find_longest_length('abcdeef')
5
>>> find_longest_length('jabjcdel')
7
Сравнение версий
Реализуйте функцию compare_version, которая сравнивает переданные версии version1 и version2. Если version1 > version2, то функция должна вернуть 1, если version1 < version2, то -1, если же version1 = version2 — 0.

Версия — это строка, в которой два числа (мажорная и минорные версии) разделены точкой, например: 12.11. Важно понимать, что версия — это не число с плавающей точкой, а несколько чисел не связанных между собой. Проверка на больше/меньше производится сравнением каждого числа независимо. Поэтому версия 0.12 больше версии 0.2.

Пример порядка версий:
0.1 < 1.1 < 1.2 < 1.11 < 13.37
>>> compare_version("0.1", "0.2")
-1
>>> compare_version("0.2", "0.1")
1
>>> compare_version("4.2", "4.2")
0

Подсказки
Разобрать строку на части, разделённые некоторой подстрокой, можно так:
>>> 'foo::bar::baz'.split('::')
['foo', 'bar', 'baz']
Вес Хэмминга
Вес Хэмминга это количество единиц в двоичном представлении числа.

Реализуйте функцию hamming_weight, которая считает вес Хэмминга.

Примеры
>>> hamming_weight(0)
0
>>> hamming_weight(4)
1
>>> hamming_weight(101)
4
Чанкование
Реализуйте функцию chunked, которая принимает на вход число и последовательность. Число которое задает размер чанка (куска). Функция должна вернуть список, состоящий из чанков указанной размерности.

При этом список должен делиться на куски-списки, строка — на строки, кортеж — на кортежи!
>>> chunked(2, ['a', 'b', 'c', 'd'])
[['a', 'b'], ['c', 'd']]
>>> chunked(3, ['a', 'b', 'c', 'd'])
[['a', 'b', 'c'], ['d']]
>>> chunked(3, 'foobar')
['foo', 'bar']
>>> chunked(10, (42,))
[(42,)]
Копилка
Реализуйте функцию visualize, которая подсчитывает сколько монет каждого номинала есть в копилке и показывает результат в виде графика. Каждый столбец графика — стопка монет определённого номинала.
Для простоты условимся, что монеты в копилке всегда есть, и их количество не ограничено, а номинал может быть любым.

Функция принимает на вход список или кортеж с числами и возвращает график в виде строки. Необязательный аргумент bar_char определяет символ, с помощью которого рисуется график. Значение по умолчанию — знак рубля (₽).
Для решения используйте встроенный инструмент — Counter.

Примеры
from solution import visualize
>>> print(visualize((10,1,1,1,1,1,20,20,20,2,2,2,2,3,3,3,3)))
5
₽₽ 4 4
₽₽ ₽₽ ₽₽ 3
₽₽ ₽₽ ₽₽ ₽₽
₽₽ ₽₽ ₽₽ 1 ₽₽
₽₽ ₽₽ ₽₽ ₽₽ ₽₽
--------------
1 2 3 10 20
>>>
>>> print(visualize((10,1,1,1,1,1,20,20,20,2,2,2,2,3,3,3,3), bar_char='$'))
5
$$ 4 4
$$ $$ $$ 3
$$ $$ $$ $$
$$ $$ $$ 1 $$
$$ $$ $$ $$ $$
--------------
1 2 3 10 20
Одинаковая чётность
Реализуйте функцию same_parity_filter, которая принимает на вход список и возвращает новый список, состоящий из элементов, у которых такая же чётность, как и у первого элемента исходного списка.

Примеры
>>> same_parity_filter([])
[]
>>> same_parity_filter([2, 0, 1, -3, 10, -2])
[2, 0, 10, -2]
>>> same_parity_filter([-1, 0, 1, -3, 10, -2])
[-1, 1, -3]
СЛОВАРИ И МНОЖЕСТВА
Преобразование DNA в RNA
ДНК и РНК это последовательности нуклеотидов.
Четыре нуклеотида в ДНК это аденин (A), цитозин (C), гуанин (G) и тимин (T).
Четыре нуклеотида в РНК это аденин (A), цитозин (C), гуанин (G) и урацил (U).
Цепь РНК составляется на основе цепи ДНК последовательной заменой каждого нуклеотида:
G -> C
C -> G
T -> A
A -> U
to_rna.py
Напишите функцию to_rna, которая принимает на вход цепь ДНК и возвращает соответствующую цепь РНК (совершает транскрипцию РНК).
Пример
>>> to_rna('ACGTGGTCTTAA')
'UGCACCAGAAUU'
Сборщик строки запроса
Query String (строка запроса) — часть URL, содержащая константы и их значения. Она начинается после вопросительного знака и идет до конца адреса.
Пример:
# query string: page=5

Если параметров несколько, то они отделяются амперсандом &
# query string: page=5&per=10


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

Пример
>>> build_query_string({'per': 10, 'page': 1})
'page=1&per=10'

Подсказки
Тесты ожидают, что параметры будут отсортированы, поэтому воспользуйтесь функцией sorted.
Чтобы собрать строку из нескольких кусков с помощью некоторого разделителя, вы можете воспользоваться таким способом:
>>> ','.join(['abc', 'cde', 'def'])
'abc,cde,def'
Римские цифры
Для записи цифр римляне использовали буквы латинского алфафита: I, V, X, L, C, D, M. Например:
1 обозначалась с помощью буквы I
10 с помощью Х
7 с помощью VII
Число 2020 в римской записи — это MMXX (2000 = MM, 20 = XX).

Реализуйте функцию to_roman, которая переводит арабские числа в римские. Функция принимает на вход целое число из диапазона от 1 до 3000, а возвращает строку с римским представлением этого числа.
Реализуйте функцию to_arabic, которая переводит число в римской записи в число, записанное арабскими цифрами.
Примеры
to_roman(1)
>>> 'I'
to_roman(59)
>>> 'LIX'
to_roman(3000)
>>> 'MMM'
to_arabic('I')
>>> 1
to_arabic('LIX')
>>> 59
to_arabic('MMM')
>>> 3000
Детектирование
Реализуйте функцию find_where, которая принимает на вход список книг и поисковый запрос и возвращает первую книгу, которая соответствует запросу. Каждая книга в списке — это словарь, содержащий её параметры, поисковый запрос — тоже словарь с параметрами.

Если совпадений не было, то функция должна вернуть None.
>>> books = [
... {'title': 'Book of Fooos', 'author': 'Foo', 'year': 1111},
... {'title': 'Cymbeline', 'author': 'Shakespeare', 'year': 1611},
... {'title': 'The Tempest', 'author': 'Shakespeare', 'year': 1611},
... {'title': 'Book of Foos Barrrs', 'author': 'FooBar', 'year': 2222},
... {'title': 'Still foooing', 'author': 'FooBar', 'year': 333},
... {'title': 'Happy Foo', 'author': 'FooBar', 'year': 4444},
... ]
...
>>> find_where(books, {'author': 'Shakespeare', 'year': 1611})
{'title': 'Cymbeline', 'author': 'Shakespeare', 'year': 1611}
Скрэббл
Реализуйте функцию-предикат scrabble, которая принимает на вход два параметра: набор символов (строку) и слово, и проверяет, можно ли из переданного набора составить это слово. В результате вызова функция возвращает True или False.
При проверке учитывается количество символов, которые нужны для составления слова, и не учитывается их регистр.
Для решения используйте встроенный инструмент — Counter.
Пример
>>> scrabble('rkqodlw', 'world')
True
>>> scrabble('avj', 'java')
False
>>> scrabble('avjafff', 'java')
True
>>> scrabble('', 'hexlet')
False
>>> scrabble('scriptingjava', 'JavaScript')
True
Слияние словарей
Реализуйте функцию merged, которая объединяет несколько словарей в один общий словарь. Функция принимает любое количество аргументов и возвращает результат в виде словаря, в котором каждый ключ содержит множество (set) уникальных значений.
Примеры
>>> from solution import merged
>>> merged({}, {}) == {}
True
>>> merged(
... {'a': 1, 'b': 2},
... {'b': 10, 'c': 100}
... ) == {'a': {1}, 'b': {2, 10}, 'c': {100}}
...
True
>>>
Подсказка
Функция может вернуть любой подобный словарю объект. Вы можете выбрать наиболее подходящий среди имеющихся в стандартной библиотеке.
ФУНКЦИИ
Композиция функций
С точки зрения математики, композиция функций f и g — новая функция z(x) = f(g(x)).

Реализуйте функцию compose, которая принимает на вход две других одноаргументных функции и возвращает новую функцию. Эта новая функция также должна принимать один аргумент и применять к нему исходные функции в нужном порядке: для функций f и g порядок применения должен выглядеть, как f(g(x)).

Примеры
Примеры ниже помогут понять, как должна работать функция:
>>> def add5(x):
... return x + 5
... def mul3(x):
... return x * 3
...
>>> compose(mul3, add5)(1)
18
>>> compose(add5, mul3)(1)
8
>>> compose(mul3, str)(1)
'111'
>>> compose(str, mul3)(1)
'3'
Увеличение двумерного списка в ФП-стиле
Это испытание представляет собой задание повышенной сложности. Помните: в самом начале обучения программированию вполне нормально не уметь решать подобные задачи!

В этом испытании вам предстоит решить ту же проблему, что вы могли решать в испытании "Увеличение двумерного списка" (вам стоит это проделать, если вы ещё не проходили то испытание).
Задача заключается в том, что нужно реализовать функцию enlarge, которая увеличивает переданное "изображение" в два раза: каждый "пиксель" удваивается по горизонтали и вертикали. Изображением служит список строк, а пиксели в нём — символы строк.

Получившаяся функция должна работать так:
>>> glider = [' * ', ' *', '***']
>>> def display(image):
... for line in image:
... print(line)
...
>>> display(glider)
*
*
***
>>> display(enlarge(glider))
**
**
**
**
******
******

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

Средства для решения задачи

Вам даны функции-комбинаторы curry и compose, а так же две простые функции pair и dup.
Функция curry превращает функцию двух аргументов в функцию от первого, возвращающую функцию от второго (такая операция называется каррированием):
>>> from operator import add
>>> add(5, 6)
11
>>> f = curry(add)
>>> f(5)(6)
11
>>> add5 = curry(add)(5)
>>> add5(10)
15
>>> from functools import reduce
>>> concat = curry(reduce)(add)
>>> concat(["Hello", ", ", "World!"])
"Hello, World!"
Функция compose берёт одну функцию, затем другую и возвращает их композицию:
>>> from operator import add, mul
>>> add5 = curry(add)(5)
>>> mul3 = curry(mul)(3)
>>> f = compose(add5)(mul3) # f(x) == add5(mul3(x))
>>> g = compose(mul3)(add5) # f(x) == mul3(add5(x))
>>> f(1) # 5 + (3 * 1)
8
>>> g(1) # 3 * (5 + 1)
18
Оставшиеся две функции элементарны:
>>> pair = lambda x: [x, x]
>>> dup = lambda x: x + x
Ваша задача описать функцию enlarge, через указанные функции и, возможно, map/filter/reduce. Имейте в виду, что задача решаема с использованием одних лишь упомянутых выше функций, не требуются даже lambda-функции!
Кстати, функция display из первого примера может быть определена и так:
>>> display = compose(print)(''.join)
>>> display(['foo', 'bar'])
foo
bar
NRZI кодирование
NRZI код (Non Return to Zero Invertive) — один из способов линейного кодирования. Обладает двумя уровнями сигнала и используется для передачи битовых последовательностей, содержащих только 0 и 1. NRZI применяется, например, в оптических кабелях, где устойчиво распознаются только два состояния сигнала — свет и темнота.

Принцип кодирования

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

Примеры
>>> find_index_of_nearest(2, []) is None
True
>>> find_index_of_nearest(0, [15, 10, 3, 4])
2
>>> find_index_of_nearest(4, [7, 5, 3, 2])
1
>>> find_index_of_nearest(4, [7, 5, 4, 4, 3])
2
Конвертер цветов
Для задания цветов в HTML и CSS используются числа в шестнадцатеричной системе счисления. Чтобы не возникало путаницы в определении системы счисления, перед шестнадцатеричным числом ставят символ решетки #, например, #135278. Обозначение цвета (rrggbb) разбивается на три составляющие, где первые два символа обозначают красную компоненту цвета, два средних — зеленую, а два последних — синюю. Таким образом каждый из трех цветов — красный, зеленый и синий — может принимать значения от 00 до FF в шестнадцатеричной системе исчисления.

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

Функция rgb2hex принимает 3 параметра (цветные компоненты) и возвращает строку. Функция должна работать как с позиционными, так и с именованными аргументами.

Примеры:
>>> rgb2hex(36, 171, 0);
'#24ab00'
>>> rgb2hex(r=36, g=171, b=0)
'#24ab00'
IP конвертер
Реализуйте и экспортируйте функции ip2int и int2ip, которые преобразовывают представление IP-адреса из десятичного формата с точками в 32-битное число в десятичной форме и обратно.

Функция ip2int принимает на вход строку и должна возвращать число. А функция int2ip наоборот: принимает на вход число, а возвращает строку.

Примеры
>>> ip2int('128.32.10.1')
2149583361
>>> ip2int('0.0.0.0')
0
>>> ip2int('255.255.255.255')
4294967295
>>>
>>> int2ip(2149583361)
'128.32.10.1'
>>> int2ip(0)
'0.0.0.0'
>>> int2ip(4294967295)
'255.255.255.255'
Интерактивные функции
Это испытание имеет повышенную сложность. Вам предстоит реализовать два декоратора, образующих маленький DSL, позволяющий превращать обычные функции в интерактивные программы.

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

Обе функции в модуле уже объявлены, но не реализованы. Вам нужно будет написать "тела" этих функций. Обе функции снабжены docstrings (в порядке исключения — на русском языке), которые поясняют назначение аргументов каждой функции.

Примеры
>>> @interactive('Calculator')
... @asks('x', 'Enter first number: ')
... @asks('y', 'Enter second number: ')
... def calc(x, y):
... return int(x) + int(y)
...
>>> calc()
Calculator
Enter first number: 42
Enter second number: 57
99
Фильтр анаграмм
Анаграммы — это слова, которые состоят из одинаковых букв. Например:
спаниель — апельсин
карат — карта — катар
топор — ропот — отпор

Реализуйте функцию filter_anagrams, которая находит все слова-анаграммы. Функция принимает исходное слово и последовательность (iterable) слов для проверки, а возвращает последовательность анаграмм.

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

Примеры
>>> list(filter_anagrams('abba', ['aabb', 'abcd', 'bbaa', 'dada']))
['aabb', 'bbaa']
>>> list(filter_anagrams('racer', ['crazer', 'carer', 'racar', 'caers', 'racer']))
['carer', 'racer']
>>> list(filter_anagrams('laser', ['lazing', 'lazy', 'lacer']))
[]
>>> list(filter_anagrams([1, 2], [[2, 1], [2, 2], [1, 2]]))
[[2, 1], [1, 2]]
Горизонтальная гистограмма
Гистограмма — это графическое представление данных в виде столбцов или колонок.

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

Необязательные параметры:
min_value — определяет минимальное значение, для которого рисуется гистограмма. По умолчанию не задан, то есть верхний стобец в гистограмме соответствует минимальному из переданных чисел.
max_value — определяет максимальное значение, для которого рисуется гистограмма. По умолчанию не задан, то есть нижний столбец в гистограмме соответствует максимальному из переданных чисел.
bar_char — символ, с помощью которого создаются стобцы в гистограмме. По умолчанию — #.

Для решения используйте встроенный инструмент — Counter.

Примеры
>>> print(histo([1, 1, 3, 4, 5]))
1|## 2
2|
3|# 1
4|# 1
5|# 1
>>> print(histo([1, 1, 3, 4, 5], bar_char = '*'))
1|** 2
2|
3|* 1
4|* 1
5|* 1
>>> print(histo([1, 1, 3, 4, 5], min_value = 3, max_value = 4))
3|# 1
4|# 1
>>> print(histo([], min_value = 1, max_value = 5))
1|
2|
3|
4|
5|
Успешных з
Валидатор IPv6
Реализуйте функцию-предикат is_valid_ipv6, которая проверяет IPv6-адреса (адреса шестой версии интернет протокола) на корректность. Функция принимает на вход строку с адресом IPv6 и возвращает True, если адрес корректный, и False, если нет.

Дополнительные условия:

Работа функции не зависит от регистра символов.
Ведущие нули в группах цифр необязательны.

Самая длинная последовательность групп нулей, например, :0:0:0: может быть заменена на два двоеточия ::. Такую замену можно произвести только один раз.
Одна группа нулей :0: не может быть заменена на ::.

Примеры
>>> from solution import is_valid_ipv6
>>> is_valid_ipv6('10:d3:2d06:24:400c:5ee0:be:3d')
True
>>> is_valid_ipv6('::1')
True
>>> is_valid_ipv6('2607:G8B0:4010:801::1004')
False
>>> is_valid_ipv6('2.001::')
False
>>>
Подсказки
IPv6
Для реализации проверки пограничных случаев изучите список IP-адресов в модуле с тестами.
ДОПОЛНИТЕЛЬНЫЕ МАТЕРИАЛЫ