Функциональное программирование
Ссылки
- haskell.org для установки
- wiki.haskell.org много материалов для изучения
- Haskell Wikibook.
Читайте разделы в Haskell Basics:
Variables and functions
,Truth values
,Type basics
,Lists and tuples
,Next steps
. В разделе Elementary Haskell: всё безTypes declarations
и без разделов о непонятном. - Кубенский А.А. Функциональное программирование. Для доступа к сайту издательства Юрайт перейдите по ссылке http://cufts.library.spbu.ru/CRDB/SPBGU/resource/306/goto и введите свои университетские логин и пароль.
- Наша лекция про функторы, аппликативные функторы и монады
Задачи
Вводные
Операции со списками
length1
длина спискаlast1
возвращает последний элемент спискаconc1
конкатенация списковpush1
дописать элемент в конец спискаrepeat1 :: Int -> Char -> String
повторить элемент указанное число разget1
получить элемент списка по его индексуrev1
перевернуть списокtake1
вернуть список, состоящий из указанного количества начальных элементов списка
Теория чисел
Пользуйтесь функциями обработки списков: map
, filter
и т.п.
- Дано число, вывести список всех его положительных делителей.
- Дано число, проверить его на простоту.
Воспользуйтесь предыдущей задачей, но не перебирайте список делителей полностью, проверьте только, какой делитель идёт после единицы. - Дано число, проверить, совершенное ли это число. Другими словами, верно ли что число равно сумме своих делителей, не считая себя. Например, 6 = 1 + 2 + 3.
- Найдите все совершенные числа от 1 до миллиона
- Решето эратосфена. Дано n, найдите все простые числа от 1 до n. Прочитайте алгоритм решета Эратосфена, и реализуйте его следующим образом: создайте список чисел от 2 до n, потом отделяйте от него первый элемент, фильтруйте остальные, которые на него делятся. Продолжайте это, пока список не кончится. Обсуждение решета Эратосфена и его реализации на Haskell см. учебник Кубенского.
- Простые близнецы. Дано n, найдите все пары простых чисел от 1 до n, отличающихся на 2.
Операции на списках
foldl
и foldr
Решите каждую задачу с помощью одной из функций: foldl
или foldr
.
Обычно с помощью одной из этих функций решение проще и естественней.
Иногда хорошее решение можно получить обеими функциями,
в этом случае попробуйте решить одну задачу двумя функциями.
Обратите внимание, что разные задачи имеют похожие решения с помощью функций свертки, они отличаются буквально в одной операции. Найдите такие задачи с похожими решениями.
- Посчитайте количество элементов в списке
- Переверните список
- Припишите один список к другому (операция
++
) - Реализуйте функцию
filter
- Реализуйте функцию
map
- Реализуйте функцию
concatMap
. Она имеет тип:(a -> [b]) -> [a] -> [b]
и делает следующее:concatMap f l
применяет функциюf
к каждому элементу спискаl
и получает списки. Все полученные списки объединяются в один. - Реализуйте
foldl
черезfoldr
. И потом наоборот. (Подсказка: переворачивание списка мы делали через foldl, поэтому его можно использовать во втором случае) - Напишите функцию
minmax
, которая возвращает минимальный и максимальный элемент списка в виде Tuple (или списка из двух элементов, если вы еще не знаете, что такое Tuple).
concatMap
Следуюшие задачи нужно решить с помощью встроенной функции concatMap
- Дан список, повторите каждый его элемент два раза.
- Опять реализуйте
filter
- Реализуйте
map
Несколько задач на алгоритмы
Эти задачи не на конкретные функции или приемы, а на то, как запрограммировать что-то содержательное. Вы можете использовать всё, что мы проходили о 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 (если вы еще не знаете, что это, то делайте список из трех элементов): все элементы, меньшие
- Сортировка слиянием.
- Напишите функцию, которая делит один список на два одинаковой длины. Или почти одинаковой, если длина нечетна. Просмотртие список функций по работе со списком из стандартной библиотеки, чтобы найти какую-нибудь функцию, которая в этом поможет.
- Напишите функцию, которая сливает два отсортированных списка в один отсортированный. Сделайте это рекурсивно, откусывая по одному элементу с начала каждого из списков.
- Теперь сама сортировка. Разделите заданный список на два равной длины. Рекурсивно сортируйте оба эти списка, потом слейте их вместе
zip
, zipWith
и unzip
Используйте функции zip, zipWith, unzip.
- Реализуйте функцию zipWithIndex: к каждому элементу списка “припишите” его индекс. Т.е. каждый элемент списка должен превратиться в тьюпл, где вначале идет индекс элемента, а потом сам элемент. Начинайте считать элементы с нуля.
- Сложите каждый элемент списка
[Int]
с его индексом. - Дан список
[Int]
, верните список, в котором все четные по счету элементы заменены на ноль - Дан список, удалите из него все элементы с четным индексом.
- Дан список двузначных чисел. Верните тьюпл из двух списков: первые цифры чисел и вторые цифры чисел. Например,
[10, 44, 31]
превращается в([1, 4, 3], [0, 4, 1])
.
Алгебраические типы данных
Не забывайте писать в конце определений типов deriving Show
, чтобы значения ваших типов можно было распечатывать.
- Тип данных “Точка на плоскости”.
- Создайте свой собственный тип данных Point (точка на плоскости) У каждой точки есть две
Int
координаты. - Реализуйте функцию vectorSum поэлементного сложения точек
- Реализуйте функцию vectorLength, которая считает длину вектора из нуля до точки
- Создайте свой собственный тип данных Point (точка на плоскости) У каждой точки есть две
- Тип данных “Фигура”
- Создайте тип данных Figure, значениями этого типа могут быть круги, для которых известен радиус. Прямоугольник, у которого известно две стороны. Равносторонний треугольник, у которого тоже известна одна сторона.
- Реализуйте функцию
area :: Figure -> Double
, которая вычисляет площадь фигуры
- Введем тип “список”:
data MyList = Empty | Cons Int MyList deriving Show
Реализуйте для такого списка операции:
- Сумма всех элементов списка
- Длина списка
- Переворачивание списка
Тип Maybe
- Реализуйте функцию
safePop :: [a] -> Maybe (a, [a])
, которая по списку возвращает пару (tuple) из его головы и хвоста. Результат заворачивается в Maybe, чтобы учесть ситуацию, когда результата нет, т.е. список пуст. - Используя фунцию
safePop
, реализуйте функциюsafeGet :: Int -> [a] -> Maybe a
, она должна возвращать элемент списка с указанным индексом.
Классы типов
- Повторите код с лекции, который создает класс
Reversible
с методомrev
. Потом реализуйте переворачиваниеInt
, cписка. - Реализуйте для типа
Point
следующие классы. Проверьте, что с типомPoint
начнут работать встроенные функцииmin
,minimum
,print
и другие, ожидающие типы классовShow
,Eq
,Ord
.Reversiable
: при переворачивании точки меняются местами координаты, и каждая из них переворачивается.Show
: точка записывается в виде строки текста как два числа через точку с запятой.Eq
: две точки можно сравнить на одинаковостьOrd
: две точки можно сравнить, при этом реально сравниваются расстояния до (0; 0)
Деревья
В этом разделе задача 1 обязательна.
Создайте алгебраический тип дерево data Tree a = EmptyTree | TreeNode a (Tree a) (Tree a)
и листовое дерево: data LeafTree a = Leaf a | InnerNode (LeafTree a) (LeafTree a)
.
Чем они отличаются? Как бы вы задали дерево с произвольным количеством детей в каждом узле?
- Реализуйте для дерева класс
Show
.- В простой версии вам нужно вывести все дерево в одну строку, расставив скобки:
Например,
1[2[5, 6], 3[_,5]]
, здесь в корне записана 1, у нее потомки 2 и 3. У двойки потомки 5 и 6, у тройки правый потомок 5. - В сложной версии сделайте красивый вывод дерева, как в команде
tree
в linux:1 ├── 2 │ ├── 5 │ └── 6 └── 3 └── 5
- В простой версии вам нужно вывести все дерево в одну строку, расставив скобки:
Например,
- Реализуйте для дерева функцию сворачивания. Она принимает на вход две функции, соответствующие
каждому из конструкторов:
f1 :: b
(для конструктора EmptyTree),f2 :: a -> b -> b -> b
(для конструктора TreeNode, мы имеем одно значение в узле и два свернутых значения из поддеревьев). И еще она принимает на вход дерево:treeFold :: b -> (a -> b -> b -> b) -> Tree a -> b
. - Аналогично, для листового дерева придумайте сигнатуру и реализуйте сворачивание.
- Для листового дерева реализуйте
listifyLeafTree
, превращение в список. Просто значения из листьев выписываются слева направо. Вы можете использовать для этого реализованное выше сворачивание. - Аналогично для обычного дерева реализуйте
listifyTreeFirst
,listifyTreeLast
,listifyTreeBetween
, это три разных превращения в список. Они отличаются тем, в какой момент корень дерева попадает в список. В начале, в конце или между тем как в список попадет левое поддерево, и до того, как попадет правое. Попробуйте начать писать превращение в список с помощью свораивания, чтобы понять, что имеется в виду. - Имея
listify
для деревьев, сделайте их представителями классаFoldable
.
Деревья поиска
-
Дерево поиска. Представьте, что у нас есть правило для того, какие значения хранятся в дереве. Значение в узле всегда не меньше всех значений в левом поддереве, и всегда меньше всех значений в правом поддереве. Реализуйте функцию добавления нового значения в дерево.
Кстати, как вы опишете тип у такой функции? Вы можете для простоты считать, что дерево поиска это
Tree Int
, но лучше считайте, что этоOrd a => Tree a
, т.е. дерево из любого упорядоченного типа. - Реализуйте функцию поиска минимального элемента в дереве поиска.
- Реализуйте функцию поиска и удаления минимального элемента в дереве поиска.
Функторы, Аппликативные функторы
Лекция: Функторы, Аппликативные функторы, Монады
- Сделайте деревья из прошлого раздела (хотя бы одно) функтром.
- * Придумайте, как можно сделать листовое дерево аппликативным функтором
- Даны два значения
Maybe Int
. Верните их сумму тоже в видеMaybe Int
. Используйте<$>
и<*>
. - Даны два списка
[Int]
. Получите новый список, где каждый элемент первого сложен с каждым элементов второго. Используйте<$>
и<*>
. - Реализуйте монаду
Log
с лекции. - Монада IO. Напишите функцию, которая читает два числа с клавиатуру и возращает их сумму
IO Int
. Воспользуйтесь этой функцией внутриmain
.