Функциональное программирование
Лекции
Лекция 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, и верните список простых чисел.