-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlec05.hs
More file actions
208 lines (151 loc) · 10.7 KB
/
lec05.hs
File metadata and controls
208 lines (151 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import Prelude hiding (foldr, foldl, length, map, maximum)
import Debug.Trace
-------------------------------------------------
-- Домашнее задание 5
-------------------------------------------------
-- 1. Выразите функции elem и filter через foldr.
-- 2. Выразите reverse через (:) и foldl.
-- 3. Объясните следующую реализацию map:
map f = foldr ((:) . f) []
-- Напишите тип каждого подвыражения.
-- 4. С помощью foldl или foldr напишите функцию horner :: [Double] ->
-- Double -> Double, которая с помощью метода Горнера (см. Википедия)
-- вычисляет значение полинома, заданного списком своих коэффициентов,
-- на втором аргументе.
-- 5. В Prelude есть функции scanl и scanr, которые похожи на foldl
-- и foldr, но они возвращают не только свертку всего списка, но
-- список, состоящий из свертков всех префиксов (соответственно, суффиксов)
-- списка. То есть
-- scanl f z [x1, x2, ..., xn] == [z, z `f` x1, (z `f` x1) `f` x2, ...]
-- scanr f z [x1, x2, ..., xn] == [..., x(n-1) `f` (xn `f` z), xn `f` z, z]
-- В некотором смысле scanl и scanr можно рассматривать как трассировку
-- foldl и foldr.
-- Напишите scanl с помощью хвостовой рекурсии и scanr с помощью
-- нехвостовой рекурсии.
-- В следующих заданиях не используйте явную рекурсию.
-- Можно использовать библиотечные функции.
-- 6. Как известно, гармонический ряд (сумма 1/k для k от 1 до
-- бесконечности) расходится. Более точно, сумма 1/k для k от 1 до n
-- при больших n ведет себя, как ln(n) + gamma, где gamma =
-- 0,5772156649... есть постоянная Эйлера. С помощью scanl, но не
-- используя явную рекурсию, напишите функцию harmonicSeriesExceeds ::
-- Double -> Int, такую что harmonicSeriesExceeds x возвращает
-- натуральное число n, при котором сумма 1/k для k от 1 до n в первый
-- раз превосходит x. Для конкретных x и соответствующих n проверьте,
-- что (sum (map (1/) [1..n-1])) <= x и (sum (map (1/) [1..n])) > x.
-- 7. См. задание в файле hw05.pdf.
-------------------------------------------------
-- Решения некоторых задач из домашнего задания 3
-------------------------------------------------
-- 3.1. Используя генератор списков, напишите функцию powerset :: [a] ->
-- [[a]], которая возвращает список всех подсписков данного списка.
-- Порядок элементов в возвращаемых списках неважен.
powerset [] = [[]]
powerset (x : xs) = let p = powerset xs in p ++ [x : s | s <- p]
-- Вместо [x : s | s <- p] можно написать map (x :) p.
-- Идея: в предположении, что powerset [2, 3] == [[], [2], [3], [2, 3]] имеем:
-- powerset [1, 2, 3] == [[], [2], [3], [2, 3]] ++ [[1], [1, 2], [1, 3], [1, 2, 3]]
-- 3.3 Определение minimum через msort.
-- msort создает бинарное дерево рекурсивных вызовов. Если msort xs
-- вызывает msort l1 и msort l2, где l1 ++ l2 == xs, то результаты
-- этих вызовов объединяются с помощью merge. Для того, чтобы получить
-- голову результата merge (а именно этого достаточно для вычисления
-- минимума в силу ленивой стратегии вычислений), функция merge должна
-- сделать одно сравнение. Таким образом, количество сравнений равно
-- числу внутренних вершин в дереве рекурсивных вызовов msort.
-- Легко доказать индукцией по бинарному дереву, что количество
-- листьев превосходит количество внутренних вершин на 1.
-- Действительно, дерево — это либо лист, либо корень с двумя
-- поддеревьями. В первом случае утверждение верно. Пусть два
-- поддерева имеют соответственно l1 и l2 листьев и i1 и i2 внутренних
-- вершин. По предположению индукции l1 = i1 + 1 и l2 = i2 + 1.
-- Количество листьев в целом дереве есть l1 + l2, а количество
-- внутренних вершин — i1 + i2 + 1 (нужно добавить корень дерева).
-- Таким образом, требуемое утверждение l1 + l2 = (i1 + i2 + 1) + 1
-- имеет место и для целого дерева.
-- Листья в дереве рекурсивных вызовов соответствуют вызовам msort на
-- одноэлементных списках, поэтому количеству листьев равно длине
-- списка, являющегося аргументом функции minimum. Значит, данная
-- реализация функции minimum делает n-1 сравнений на списке длины n,
-- как и любая другая оптимальная реализация.
-- Убедиться в этом можно с помощью функции trace :: String -> a -> a,
-- которая находится в модуле Debug.Trace. Функция trace печатает
-- свой первый аргумент и возвращает второй. Функцию merge из lec03.hs
-- можно переписать следующим образом.
merge [] ys = ys
merge xs [] = xs
merge l1@(x : xs) l2@(y : ys)
| trace (show x ++ " " ++ show y) False = undefined
| x < y = x : merge xs l2
| otherwise = y : merge l1 ys
-- Таким образом, каждый раз, когда два числа сравниваются, они печатаются.
-- 3.5. Напишите функцию primes :: [Integer], которая вычисляет бесконечный
-- список простых чисел с помощью алгоритма "Решето Эратосфена".
primes :: [Integer]
primes = sieve [2..]
where sieve (p : xs) = p : sieve [p' | p' <- xs, p' `mod` p /= 0]
-- Можно слегка оптимизировать функцию, начав со списка нечетных чисел.
primes1 :: [Integer]
primes1 = 2 : sieve [3,5..]
where sieve (p : xs) = p : sieve [p' | p' <- xs, p' `mod` p /= 0]
-------------------------------------------------
-- Конспект лекции 5 от 15.03.2021
-------------------------------------------------
-- Напоминание: эта тема хорошо описана в книге
-- Макеева, ссылка на которую находится на source.unn.ru.
-- Содержание: левая и правая свертки
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
-- foldr f z [1, 2, 3] =
-- f 1 (foldr f z [2, 3]) =
-- f 1 (f 2 (foldr f z [3]))
-- f 1 (f 2 (f 3 (foldr f z [])))
-- f 1 (f 2 (f 3 z)) =
-- 1 `f` (2 `f` (3 `f` z))
-- f получает два аргумента. Первый — это голова списка. Второй —
-- результат обработки хвоста списка. Задача f — превратить
-- результат обработки хвоста (рекурсивный вызов) в результат
-- обработки всего списка.
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
-- foldl f z [1, 2, 3] =
-- foldl f (f z 1) [2, 3] =
-- foldl f (f (f z 1) 2) [3] =
-- foldl f (f (f (f z 1) 2) 1) [] =
-- (f (f (f z 1) 2) 1) =
-- ((z `f` 1) `f` 2) `f` 3
-- length через foldr
-- x есть голова списка
-- y есть длина хвоста
length = foldr (\x y -> y+1) 0
-- Обратите внимание, что (\x y -> y+1) == const (+1), а
-- (\x y -> x+1) == const . (+1) (домашнее задание 4).
-- Объясните эти равенства функций.
-- (++) через foldr
-- Напоминание определения конкатенации двух списков (++)
-- [] ++ l2 = l2
-- (h : t) ++ l2 = h : (t ++ l2)
append l1 l2 = foldr (\h p -> h : p) l2 l1
-- Более коротко:
append' l1 l2 = foldr (:) l2 l1
-- map через foldr
map' f l = foldr (\h p -> f h : p) [] l
-- l можно опустить из левой и правой части
-- Обратите внимание на порядок аргументов функции f, являющейся
-- первым аргументом foldr и foldl. В foldr элемент списка является
-- первым аргументом, а в foldl — вторым. Если `f` используется как
-- инфиксный оператор, то элемент списка является левым аргументом `f`
-- в foldr и правым — в foldl. Это соответствует расстановке скобок.
-- В Prelude имеются функции foldl1 и foldr1, похожие на foldl и
-- foldr, но которые работают на непустых списках и используют
-- крайние элементы списка как начальное значение. Из-за этого
-- результат функции имеет тот же тип, что и элементы списка. Это не
-- обязательно верно для foldl и foldr.
-- (Замечание: Реальные типы функций foldr и т.п. в Prelude более общие.)
-- foldr :: (a -> b -> b) -> b -> [a] -> b
-- foldl :: (b -> a -> b) -> b -> [a] -> b
-- foldr1 :: (a -> a -> a) -> [a] -> a
-- foldr1 :: (a -> a -> a) -> [a] -> a
-- maximum не определен на пустом списке, поэтому используем foldl1
maximum l = foldl1 max l