Функциональное программирование
Лекции
Лекция 1. Конспект
Лекция 2. Конспект
Лекция 3. Конспект
Лекция 4. Конспект
Лекция 5. Функции flip
, $
, .
, алгебраические типы. Конспект
Лекция 6. Рисование дерева, ленивые вычисления. Конспект
Лекция 7. Классы типов. Конспект
Лекция 8. Классы типов, Фигуры. Maybe Конспект
Лекция 9. Числовые типы и классы. Создание проекта STACK. Конспект
Лекция 10. Map. Функторы. Конспект
Лекция 11. Алгебраические типы с именованными полями. Аппликативные функторы Конспект. Рисованные картинки стр1, стр2.
Лекция 12. Ввод/Вывод. Монады. Конспект. Рисованные картинки стр.
Лекция 13. Продолжение. Монада Random. Конспект
Лекция 14. Готовая монада Random. Конспект
Задания для зачёта
- Реализуйте генератор задачек на сложение чисел, это должно быть значение типа
Random (String, String)
. При генерации должны получаться пары значений типа("Чему равно 3 + 8?", "Ответ: 11")
. Это должны быть задачи на сложение двух чисел от 1 до 10. Используйте do-нотацию. Проверьте результат с помощьюgenerateN
. - Реализуйте функцию
filterGerenator :: (a -> Boolean) -> Random a -> Random a
, эта функция генерирует случайные значения до тех пор, пока не выполнится условие. Например,filterGenerator even (randomInRange 1 10)
создаёт генератор, который генерирует четные числа от 1 до 10. - Переделайте задачу 1 так, чтобы в ответе всегда получалась сумма больше 10. Т.е. нужно генерировать «сложные» задачи, где при сложении результат переходит через десяток.
- Сгенерируйте «много» пар чисел ($K$ штук) от $-N$ до $N$, где $N = 1000$. Это координаты точек в квадрате. Посчитайте, какая часть из этих точек попала в круг с радиусом $N$ и с центром в $0$, т.е. для скольких точек выполняется ($x^2 + y^2 \le N^2$). Если таких точек $L$, можно оценить число $\pi \approx \frac{4L}{N}$.
Задачи
Вводные
- Напишите вычисление факториала с помощью хвостовой рекурсии.
Операции со списками
Эти задачи нужно решить с хвостовой рекурсией и без хвостовой рекурсии, если без хвостовой рекурсии получается короче.
length1
длина списка;last1
возвращает последний элемент списка;push1
дописать элемент в конец списка;repeat1 :: Int -> Char -> String
повторить элемент указанное число раз;conc1
конкатенация списков;get1
получить элемент списка по его индексу;rev1
перевернуть список;take1
вернуть список, состоящий из указанного количества начальных элементов списка;filter1
— это аналог встроенной функцииfilter
;map1
— это аналог встроенной функцииmap
;
Теория чисел
Пользуйтесь функциями обработки списков: map
, filter
и т.п.
- Дано число, вывести список всех его положительных делителей.
- Дано число, проверить его на простоту.
Воспользуйтесь предыдущей задачей, но не перебирайте список делителей полностью, проверьте только, какой делитель идёт после единицы. - Дано число, проверить, совершенное ли это число. Другими словами, верно ли что число равно сумме своих делителей, не считая себя. Например, 6 = 1 + 2 + 3.
- Найдите все совершенные числа от 1 до миллиона
- Решето эратосфена. Дано n, найдите все простые числа от 1 до n. Прочитайте алгоритм решета Эратосфена, и реализуйте его следующим образом: создайте список чисел от 2 до n, потом отделяйте от него первый элемент, фильтруйте остальные, которые на него делятся. Продолжайте это, пока список не кончится. Обсуждение решета Эратосфена и его реализации на Haskell см. учебник Кубенского.
- Простые близнецы. Дано n, найдите все пары простых чисел от 1 до n, отличающихся на 2.
Операции на списках
concatMap
Следуюшие задачи нужно решить с помощью встроенной функции concatMap
- Дан список, повторите каждый его элемент два раза.
- Опять реализуйте
filter
- Реализуйте
map
zip
, zipWith
и unzip
Используйте функции zip, zipWith, unzip.
- Реализуйте функцию zipWithIndex: к каждому элементу списка “припишите” его индекс. Т.е. каждый элемент списка должен превратиться в тьюпл, где вначале идет индекс элемента, а потом сам элемент. Начинайте считать элементы с нуля.
- Сложите каждый элемент списка
[Int]
с его индексом. - Дан список
[Int]
, верните список, в котором все четные по счету элементы заменены на ноль - Дан список, удалите из него все элементы с четным индексом.
- Дан список двузначных чисел. Верните тьюпл из двух списков: первые цифры чисел и вторые цифры чисел. Например,
[10, 44, 31]
превращается в([1, 4, 3], [0, 4, 1])
.
foldl
и foldr
Решите каждую задачу с помощью одной из функций: foldl
или foldr
.
Обычно с помощью одной из этих функций решение проще и естественней.
Иногда хорошее решение можно получить обеими функциями,
в этом случае попробуйте решить одну задачу двумя функциями.
Обратите внимание, что разные задачи в этом блоке имеют очень похожие решения, они отличаются буквально в одной операции. Найдите такие задачи с похожими решениями.
- Посчитайте количество элементов в списке
- Переверните список
- Припишите один список к другому (операция
++
) - Реализуйте функцию
filter
- Реализуйте функцию
map
- Реализуйте функцию
concatMap
. Она имеет тип:(a -> [b]) -> [a] -> [b]
и делает следующее:concatMap f l
применяет функциюf
к каждому элементу спискаl
и получает списки. Все полученные списки объединяются в один. - Реализуйте
foldl
черезfoldr
. И потом наоборот. (Подсказка: переворачивание списка мы делали через foldl, поэтому его можно использовать во втором случае) - Напишите функцию
minmax
, которая возвращает минимальный и максимальный элемент списка в виде Tuple.
Несколько задач на алгоритмы
Эти задачи не на конкретные функции или приемы, а на то, как запрограммировать что-то содержательное. Вы можете использовать всё, что мы проходили о Haskell.
- Алгоритм быстрого возведения в степень. Положим, нам нужно вычислить \(x^n\). Запишем следующее равенство:
$Answer = a^b \cdot c$. Оно верное, если \(a = x\), \(b = n\), \(с = 1\).
Теперь выполняем следующие шаги:
- Если \(b\) четно, то заменяем \(a \rightarrow a^2\), а \(b \rightarrow b/2\). Теперь \(Answer = (a^2)^{b/2}\cdot c\)
- Если \(b\) нечетно, то заменяем \(b \rightarrow b-1\), \(с \rightarrow c\cdot a\). Теперь \(Answer = a^{b-1} \cdot (a \cdot c)\)
Когда \(b\) станет равно нулю, мы получим \(Answer = a^0 \cdot c\), т.е. ответ это \(c\). Реализуйте этот алгоритм функционально, с хвостовой рекурсией
- Быстрая сортировка
- Напишите функцию, которая по списку и заданному числу возвращает три списка в виде Tuple: все элементы, меньшие
v
; все элементы, равныеv
; все элементы, большиеv
. - Напишите функцию, которая сортирует массив рекурсивно: Вызывает функцию из предыдущего пункта для первого элемента массива. Рекурсивно сортирует первый и третий списки, соединяет их опять в один список.
- Напишите функцию, которая по списку и заданному числу возвращает три списка в виде Tuple: все элементы, меньшие
- Сортировка слиянием.
- Напишите функцию, которая делит один список на два одинаковой длины. Или почти одинаковой, если длина нечетна. Просмотртие список функций по работе со списком из стандартной библиотеки, чтобы найти какую-нибудь функцию, которая в этом поможет.
- Напишите функцию, которая сливает два отсортированных списка в один отсортированный. Сделайте это рекурсивно, откусывая по одному элементу с начала каждого из списков.
- Теперь сама сортировка. Разделите заданный список на два равной длины. Рекурсивно сортируйте оба эти списка, потом слейте их вместе.
Алгебраические типы данных
Не забывайте писать в конце определений типов deriving Show
, чтобы значения ваших типов можно было распечатывать.
- Тип данных “Точка на плоскости”.
- Создайте свой собственный тип данных Point (точка на плоскости) У каждой точки есть две
Int
координаты. - Реализуйте функцию vectorSum поэлементного сложения точек
- Реализуйте функцию vectorLength, которая считает длину вектора из нуля до точки
- Создайте свой собственный тип данных Point (точка на плоскости) У каждой точки есть две
- Тип данных “Фигура”
- Создайте тип данных Figure, значениями этого типа могут быть круги, для которых известен радиус. Прямоугольник, у которого известно две стороны. Равносторонний треугольник, у которого тоже известна одна сторона.
- Реализуйте функцию
area :: Figure -> Double
, которая вычисляет площадь фигуры
- Введем тип “список”:
data MyList = Empty | Cons Int MyList deriving Show
Реализуйте для такого списка операции:
- Сумма всех элементов списка
- Длина списка
- Переворачивание списка
Деревья
Создайте алгебраический тип дерево data Tree a = EmptyTree | TreeNode a (Tree a) (Tree a)
и листовое дерево: data LeafTree a = Leaf a | InnerNode (LeafTree a) (LeafTree a)
.
Чем они отличаются? Чем Tree
отличается от дерева, введенного в лекции в задаче о рисовании дерева? Как бы вы задали дерево с произвольным количеством детей в каждом узле?
- Посчитайте количество элементов в дереве и листьев в листовом дереве.
- Сложите все числа в
Tree Int
и вLeafTree Int
. - Дано число n, создайте полное дерево из
n
уровней: у корня дерева два потомка, у каждого потомка по два потомка, у каждого потомка тоже по два потомка, и так $n$ раз. В дереве из $n$ уровней должно получиться $2^n - 1$ непустых узлов. - Исправьте код рисования дерева из лекции так, чтобы он работал для дерева
Tree Int
из этой задачи. Считайте, что узелLeaf
из конспекта — это узелTreeNode
, у которого оба потомкаEmptyTree
. При необходимости нарисоватьEmptyTree
, рисуйте пустую строку. При этом в дереве будут появляться линии, ведущие к пустомуEmptyTree
, но мы можем считать, что это нормально. Если хотите усложнить, не рисуйте линии, ведущие кEmptyTree
.Деревья поиска
Представьте, что у нас есть правило для того, как в числовом дереве Tree Int
хранятся значения: значение в узле всегда не меньше всех значений в левом поддереве, и всегда
меньше всех значений в правом поддереве. (Если вы уже знаете классы типов, то тип дерева должен быть Ord a => Tree a
)
!(Двоичное дерево поиска, wikipedia)[https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Binary_search_tree.svg/274px-Binary_search_tree.svg.png]
- Реализуйте функцию добавления нового значения в дерево.
- Реализуйте функцию проверки, есть ли в дереве указанное значение.
- Реализуйте функцию поиска максимального элемента в дереве поиска.
- Реализуйте функцию поиска и удаления максимального элемента в дереве поиска, функция возвращает пару из значения максимума и нового дерева без максимума.
- Реализуйте функуцию поиска следующего значения в дереве поиска. Т.е. дано число, и вы возвращаете следующее по порядку число из дерева.
- Реализуйте аналогичные операции для Декартового дерева.
Бесконечные списки
- Создайте бесконечный список степеней двойки:
[1, 2, 4, 8, 16, ...]
. Попробуйте два способа.map
для возведения в степень и списка[1..]
и умножение на 2 предыдущего значения. - Для заданного числа $n$ создайте бесконечный список, который начинается на $n$, а каждое следующее число равно $n/2$, если $n$ — четно, и $3n+1$ иначе.
- $\varphi_0=1$, $\varphi_1=1$, $\varphi_n = \varphi_{n-2}-\varphi_{n-1}$.
- $f_0=1$, $f_1=2$, $f_2=3$, $f_n = f_{n-2}f_{n-3}-f_{n-1}$.
- Создайте бесконечный список 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, … Каждое число повторяется столько раз, чему оно равно.
- Даны два списка, возможно, бесконечных. Верните список, где значения из списков чередуются. Например,
alternate [1, 2, 3, 4, 5] [10, 20, 30]
будет[1, 10, 2, 20, 3, 30, 4, 5]
. - Даны два списка, возможно, бесконечных. Это коэффициенты многочлена. Например, список
[1,2,3,0,5]
соответствует многочлену $1 + 2x+3x^2+5x^4$. Верните сумму этих многочленов. Фактически, вам надо сделатьzipWith (+) poly1 poly2
, но это обрубит ответ до более короткого списка. - В условиях предыдущей задачи верните произведение многочленов.
- Верните бесконечную последовательность чисел Каталана. $c_0=1$, $c_1=1$, $c_n = \sum_i=0^{n-1} c_ic_{n-1-i}$. Например, $c_2 = c_0c_1 + c_1c_0=2$, $c_3=c_0c_2 + c_1c_1 + c_2c_0 = 5$, далее 1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, … http://oeis.org/A000108
- [http://oeis.org/A000002]
- Разберите в учебнике Кубенского, как правильно писать решето эратосфена на Haskell, и верните список простых чисел.