Skip to content

Commit 4ab2c30

Browse files
committed
add course lectures, control works and project
0 parents  commit 4ab2c30

22 files changed

+4969
-0
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Functional programming course

Diff for: control_works/addtional.hs

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
3+
import Data.Semigroup
4+
import Data.Monoid
5+
import Control.Monad.Writer
6+
7+
-- 1. Prelude говорит, то значением выражения div x y для целых x и y
8+
-- является частное x и y, округленное вниз (то есть к минус бесконечности).
9+
-- Однако выражение
10+
11+
-- -7 `div` 3
12+
13+
-- возвращает -2, а не -3, как можно было ожидать. Объясните подробно,
14+
-- почему так получается и как можно получить правильный результат
15+
-- целочисленного деления -7 на 3.
16+
17+
-- Указание. Можно пользоваться командой :i интерпретатора GHCi,
18+
-- печатающей информацию о введенных именах. Полезная информация также
19+
-- содержится в Prelude и разделах 3.4 и 4.4.2 Haskell 2010 Language
20+
-- Report.
21+
22+
-- 2. Следующая задача была предложена на контрольной в середине
23+
-- весеннего семестра, однако все решения были излишне сложными.
24+
25+
-- Без использования рекурсии напишите функцию abbrev :: [String] -> String,
26+
-- которая в заданном списке имен людей выполняет сокращение всех
27+
-- имен, кроме фамилии, до инициалов. Фамилией считается последнее
28+
-- слово. Например:
29+
30+
-- > putStrLn $ abbrev ["Синицин"]
31+
-- "Синицин"
32+
-- > putStrLn $ abbrev ["Сергей", "Есенин"]
33+
-- "С.Есенин"
34+
-- putStrLn $ abbrev ["Игорь", "Федорович", "Поддубный"]
35+
-- "И.Ф.Поддубный"
36+
-- putStrLn $ abbrev ["Иоганн", "Хризостом", "Вольфганг", "Амадей", "Моцарт"]
37+
-- "И.Х.В.А.Моцарт"
38+
39+
-- Решение должно удовлетворять следующим условиям.
40+
41+
-- (а) Оно должно состоять из одной строчки, не считая объявления типа.
42+
-- (б) Следует использовать правую свертку для непустых списков.
43+
-- (в) Функция должна иметь временную сложность O(n), где n — длина
44+
-- списка, но не общая длина слов в списке.
45+
-- (г) Не следует использовать функцию (++) там, где можно обойтись
46+
-- более простыми функциями.
47+
48+
abbrev :: [String] -> String
49+
abbrev = undefined
50+
51+
-- 3. Напишите рекурсивную функцию
52+
53+
-- foldMapList :: Monoid m => (a -> m) -> [a] -> m
54+
55+
-- которая может использоваться для объявления членства типа списков
56+
-- в классе Foldable.
57+
58+
-- 4. В Prelude функции foldr определена через foldMap следующим образом.
59+
60+
-- foldr f z t = appEndo (foldMap (Endo . f) t) z
61+
62+
-- С учетом вашего ответа на предыдущий вопрос объясните, как работает
63+
-- это определение, если t — список, и почему оно эквивалентно
64+
-- обычному определению из лекции 5.
65+
66+
-- 5. Рассмотрим следующие определения.
67+
68+
l :: [String]
69+
l = ["ab", "cd", "ef"]
70+
71+
f1 :: [String] -> String -> String
72+
f1 = mconcat . map showString
73+
74+
f2 :: [String] -> String -> String
75+
f2 = appEndo . mconcat . map (Endo . showString)
76+
77+
-- Объясните, что возвращают f1 и f2, в чем между ними разница и какова ее причина.
78+
79+
-- Указание. Определение моноидных операций на различных типах, в том
80+
-- числе на типе функций (a -> b) и типе эндоморфизмов (Endo a),
81+
-- находятся в модуле Data.Semigroup.
82+
83+
-- 6. Напишите функцию
84+
85+
minMax :: [Int] -> (Int, Int)
86+
minMax = undefined
87+
88+
-- которая возвращает пару, состоящую из минимального и максимального
89+
-- элементов непустого списка. Она должна использовать следующую
90+
-- рекурсивную функцию, которая выполняет основную работу.
91+
92+
minMaxLoop :: [Int] -> Writer (Min Int, Max Int) ()
93+
minMaxLoop = undefined
94+
95+
-- Определения типов Min a и Max a, являющихся моноидами с операциями
96+
-- min и max, соответственно, находятся в модулях Data.Semigroup и
97+
-- Data.Monoid. Они аналогичны моноидам Sum a и Product a,
98+
-- рассмотренным на лекциях.
99+
100+
-- Функция minMaxLoop не должна явно использовать операторы сравнения, а также
101+
-- функции min, max, minimum и maximum. В определении minMax можно использовать
102+
-- функцию execWriter, определенную в Control.Monad.Writer.
103+
104+
-- 7. Подставьте определения функций flip и (.) из Prelude в терм
105+
-- flip (.) и, выполняя по одной редукции за шаг, найдите нормальную
106+
-- форму этого терма.

Diff for: control_works/final.hs

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import Data.Semigroup
2+
import Data.Monoid
3+
import Control.Monad.Writer
4+
5+
-- 1. Какое из выражений Haskell, приведенных ниже, соответствует
6+
-- выражению chores(mow(lawn), repair(leaking + faucet)) на языках
7+
-- C, Java или Python?
8+
9+
-- (а) chores (mow (lawn)) repair (leaking + faucet)
10+
-- (б) chores (mow lawn) repair (leaking + faucet)
11+
-- (в) chores (mow lawn) (repair (leaking + faucet))
12+
-- (г) chores (mow lawn) (repair leaking + faucet)
13+
14+
-- Ответ: в)
15+
16+
-- 2. Существует ли выражение e и типы t1, t2, такие что (e :: t1) и
17+
-- (e :: t2) возвращают разные значения? Если да, приведите пример и
18+
-- напишите, когда определяется способ вычисления значения e: во время
19+
-- компиляции или во время исполнения программы. Если таких значений и
20+
-- типов не существует, объясните, почему.
21+
22+
-- Да, существует:
23+
-- 3 <> 3 :: Sum Int
24+
-- Sum {getSum = 6}
25+
-- 3 <> 3 :: Product Int
26+
-- Product {getProduct = 9}
27+
-- Это выражение может вычисляться по-разному в зависимости от своего типа, способ вычисления
28+
-- определяется во время компиляции.
29+
30+
-- 3. Рассмотрим терм uncurry (flip (,)).
31+
-- (а) Найдите его тип.
32+
-- (б) Подставьте в терм определения функций uncurry и flip из Prelude
33+
-- и напишите цепочку редукций, которая заканчивается нормальной формой.
34+
-- (в) Объясните, что делает функция, представленная исходным термом.
35+
36+
-- Напоминание: (,) :: a -> b -> (a, b) есть конструктор упорядоченных
37+
-- пар.
38+
-- Ответ:
39+
--а) uncurry (flip (,)) :: (b, a) -> (a, b)
40+
41+
--б) Выпишем типы используемых функций:
42+
-- uncurry :: (a -> b -> c) -> (a, b) -> c
43+
-- flip :: (a -> b -> c) -> b -> a -> c
44+
-- (,) :: a -> b -> (a, b)
45+
46+
-- Подстановка по шагам:
47+
-- 1. flip (,) : (a -> b -> (a, b)) -> b -> a -> (a, b)
48+
-- это редуцируется до flip (,) : b -> a -> (a, b) - исключается тип для (,)
49+
-- 2. uncurry (flip (,)) : (b -> a -> (a, b)) -> (b, a) -> (a, b)
50+
-- далее рудуцируется часть для flip (,) : b -> a -> (a, b)
51+
-- 3. Получаем
52+
-- uncurry (flip (,)) : (b, a) -> (a, b)
53+
-- нормальную форму
54+
55+
--в) Принимает на вход кортеж (b, a), на выходе получается перевернутый кортеж (a, b)
56+
57+
-- 4. Большинство студентов сделало задание 16 в Formula.hs
58+
-- неправильно. В связи с этим рассмотрим следующую задачу. Тип
59+
-- деревьев с произвольным коэффициентом ветвления задается так.
60+
61+
data Tree i a = Leaf a | Node (i -> Tree i a)
62+
63+
-- Тип i выступает в роли индексного множества. Поддеревья каждого
64+
-- внутреннего узла индексируются элементами i. Так, если i = Bool,
65+
-- то это двоичное дерево, если i задано определением
66+
-- data I = I1 | I2 | I3
67+
-- то это троичное дерево, а если i = Int, то коэффициент ветвления
68+
-- равен бесконечности.
69+
70+
-- Объявите конструктор типов Tree i членом класса Foldable,
71+
-- определив функцию foldMap.
72+
73+
instance (Bounded i, Enum i) => Foldable (Tree i) where
74+
foldMap f (Leaf x) = f x
75+
foldMap f (Node n) = foldr1 (<>) (map (foldMap f . n) [minBound..maxBound])
76+
77+
-- Напишите функцию fringe, которая возвращает список элементов,
78+
-- хранящихся в листьях дерева. Функция fringe не должна явно
79+
-- использовать рекурсию, а вместо этого должна использовать foldMap с
80+
-- моноидом эндоморфизмов на типе списков. Обратите внимание, что это
81+
-- не то же самое, что моноид списков.
82+
83+
fringe :: (Bounded i, Enum i) => Tree i a -> [a]
84+
fringe t = appEndo (foldMap(Endo . (\x y->[x] ++ y)) t) []
85+
86+
-- Объявим бинарное дерево
87+
type BinaryTree = Tree Bool Int
88+
89+
insert :: BinaryTree -> BinaryTree -> Bool -> BinaryTree
90+
insert left right idx
91+
|idx == True = left
92+
|otherwise = right
93+
94+
tree = Node (insert(Leaf 1) (Leaf 2))
95+
tree2 = Node (insert (Node (insert (Leaf 1) (Leaf 2))) (Node (insert (Leaf 3) (Leaf 4))) )
96+
97+
-- 5. Почти все студенты неправильно поняли определение монотонной
98+
-- булевой функции и соответственно неправильно написали функцию
99+
-- monotone в проекте. В связи с этим напишите аналогичную функцию,
100+
-- которая принимает булеву функцию f от n аргументов и проверяет,
101+
-- является ли она монотонной. Определение монотонной функции и
102+
-- алгоритм проверки на монотонность см. в книге:
103+
-- Гаврилов Г.П., Сапоженко А.А. Задачи и упражнения по дискретной
104+
-- математике. М.: Физматлит, 2005
105+
-- на с. 75, а определение используемого при этом частичного порядка на
106+
-- наборах аргументов — на с. 10.
107+
108+
allEnvs:: Int -> [[Bool]]
109+
allEnvs 0 = [[]]
110+
allEnvs n = [x:xs | x <- [False, True], xs <- allEnvs (n-1)]
111+
112+
compareXY :: ([Bool], [Bool]) -> Bool
113+
compareXY ([], []) = True
114+
compareXY (x:xs, y:ys) =
115+
not (x && not y ) && compareXY (xs, ys)
116+
117+
monotone' :: [Bool] -> Bool
118+
monotone' [x] = True
119+
monotone' lst =
120+
let halves = splitAt (length lst `div` 2) lst
121+
in compareXY halves && monotone' (fst halves) && monotone' (snd halves)
122+
123+
monotone :: Int -> ([Bool] -> Bool) -> Bool
124+
monotone n_args func =
125+
let values = map func (allEnvs n_args)
126+
in monotone' values
127+
128+
-- 6. Напишите функцию
129+
130+
avg :: [Int] -> Float
131+
avg lst =
132+
let num_and_sum = snd (runWriter (numSum lst))
133+
num = getSum (fst num_and_sum )
134+
sum = getSum (snd num_and_sum )
135+
in fromIntegral sum / fromIntegral num
136+
137+
-- которая возвращает среднее арифметическое списка. Эта функция
138+
-- должна использовать функцию
139+
-- numSum :: [Int] -> Writer (..., ...) ()
140+
141+
numSum :: [Int] -> Writer (Sum Int, Sum Int) ()
142+
numSum [] = writer (mempty , (0, 0))
143+
numSum (x:xs) = do
144+
numSum xs
145+
tell (1, Sum x)
146+
return mempty
147+
148+
-- с некоторыми типами вместо многоточий, которая вычисляет количество
149+
-- и сумму элементов в списке. Функции numSum и avg должны быть
150+
-- аналогичны одноименным методам в следующей программе на Java, за
151+
-- исключением того, что прохождение списка должно, как обычно,
152+
-- использовать рекурсию, а не цикл for.
153+
154+
-- public class Avg {
155+
-- private int s;
156+
-- private int n;
157+
--
158+
-- private void update(int x) { s += x; n++; }
159+
--
160+
-- private void numSum(int[] a) {
161+
-- for (int i = 0; i < a.length; i++)
162+
-- update(a[i]);
163+
-- }
164+
--
165+
-- public float average(int[] a) {
166+
-- s = 0; n = 0;
167+
-- numSum(a);
168+
-- return (float)s/n;
169+
-- }
170+
--
171+
-- public static void main(String[] args) {
172+
-- Avg avg = new Avg();
173+
-- int[] a = {1, 3, 2, 6, 4, 5};
174+
-- System.out.println("The average is " + avg.average(a));
175+
-- }
176+
-- }
177+
178+
-- Другой способ сделать avg с монадой writer :
179+
avg2 :: [Int] -> Float
180+
avg2 lst =
181+
let sum_num = runWriter (numSum2 lst)
182+
sum = fst sum_num
183+
num = getSum (snd sum_num)
184+
in fromIntegral sum / fromIntegral num
185+
186+
numSum2 :: [Int] -> Writer (Sum Int) Int
187+
numSum2 [] = return 0
188+
numSum2 (x:xs) = do
189+
t <- numSum2 xs
190+
tell 1
191+
return (x + t)
192+
193+
-- 7. Напишите функцию allSums, которая возвращает суммы всех
194+
-- подпоследовательностей списка-аргумента. Элементы
195+
-- подпоследовательности могут быть разделены в исходном списке.
196+
-- Порядок сумм неважен, и некоторые суммы могут совпадать. Например,
197+
198+
-- > allSums []
199+
-- [0]
200+
-- > allSums [2]
201+
-- [2,0]
202+
-- > allSums [1,3,4]
203+
-- [8,4,5,1,7,3,4,0]
204+
205+
-- Используйте монаду списков и do-нотацию. Может быть полезным
206+
-- использовать список [True, False]. Библиотечные функции (кроме (+)
207+
-- и return) в основном решении использовать нельзя, но можно написать
208+
-- альтернативные решения с функциями из Control.Monad.
209+
210+
allSubsets :: (Monad m) => (m Bool) -> [Int] -> m [Int]
211+
allSubsets _ [] = return []
212+
allSubsets p (x:xs) = do
213+
flg <- p
214+
ys <- allSubsets p xs
215+
return (if flg then x:ys else ys)
216+
217+
allSums :: [Int] -> [Int]
218+
allSums xs = [subsetSum s| s <- all_subsets]
219+
where all_subsets = allSubsets [True, False] xs
220+
subsetSum :: [Int] -> Int
221+
subsetSum [] = 0
222+
subsetSum (x:xs) = x + subsetSum xs
223+
224+
--Решение с использованием библиотечных функций:
225+
allSums2 xs = map sum $ filterM(const [True, False]) xs

Diff for: control_works/midterm-problem3.pdf

123 KB
Binary file not shown.

0 commit comments

Comments
 (0)