330 likes | 504 Views
Контрольная работа. 20 октября примерно на час Примерно 10 простых задач Накапливающие параметры Работа со списками Списки списков Пары data (типа торты , фильмы ) map foldr Деревья List comprehension. Как будет проходить
E N D
Контрольная работа • 20 октября • примерно на час • Примерно 10 простых задач • Накапливающие параметры • Работа со списками • Списки списков • Пары • data (типа торты, фильмы) • map • foldr • Деревья • List comprehension • Как будет проходить • Можно пользоваться любыми материалами(интернетом, компьютерами, распечатками, книгами и т.д.) • Нельзя пользоваться чьей-либо помощью в любой форме • Сдавать можно будет на листке, на любом носителе или послать по почте • Могут быть мелкие синтаксические ошибки
swapSymbols swapSymbols xs = map (\x -> if x == '?' then '!' else if x == '!' then '?' else x) xs swapSymbols xs = map (\x -> case x of '?' -> '!' '!' -> '?' x -> x) xs swapSymbols xs = map swap xs where swap '?' '!' swap '!' '?' swap x x
Задача на листочке f n [1,-1,1,-1,1,…] (n раз) f n = map (\i -> что-то) [1..n] • что-то – то, что надо сгенерировать для i-того элемента 1 1 2 -1 3 1 4 -1 5 1 … map (\i -> if i `mod` 2 == 1 then 1 else -1) [1..n] • Еще вариант: map (\i -> (-1) ^ i)) [0..n-1] или map (\i -> (-1) ^ mod i 2)) [0..n-1]
Задача на листочке – без map • Вариант 1 f n = f’ 1 n f’ _ 0 = [] f’ x n = x : f’ (-x) (n-1) • Вариант 2 f 0 = [] f 1 = [1] f n = 1:-1:f (n-2) • Типичное замечание: • ++ [x] • Неэффективно :(
multTable multTable 3 [[1,2,3], [2,4,6], [3,6,9]] map (\i-> что-то) [1..n] что-то = сгенерировать i-ю строку i-я строка = [i, 2*i, 3*i, 4*i, … n*i] map (\j->i*j) [1..n] multTable n = map (\i -> map (\j -> i*j ) [1..n]) ) [1..n] • Примерно как for (i = 1; i<=n; i++) { for (j = 1; j<=n; j++) { cout << i*j; } } 1 строка 2строка 3строка
repeatFunc repeatFunc f n = f.f.f … .f - n раз • полезно 'забыть' про смысл оператора (.) repeatFunc f n = f . repeatFunc f (n-1) • Нерекурсивный случай repeatFunc f 1 = f repeatFunc f 0 = ? \x -> x • id – тождественное отображение id x = x • Все вместе: repeatFunc f 0 = id repeatFunc f n = f . repeatFunc f (n-1)
countOdd • countOddxs = foldr (\i res -> if mod i 2 == 1 then res+1 else res) 0 xs Еще варианты: • Выносим res из if countOddxs = foldr (\i res -> res + if mod i 2 == 1 then 1 else 0) 0 xs • Вообще без if countOddxs = foldr (\i res -> res + mod i 2) 0 xs • Короче с каррингом countOdd = foldr (\i res -> res + mod i 2) 0
countOdd1 • countOdd1 xs = length ( filter (\i -> mod i 2 == 1) xs) • countOdd1 xs = length ( filter odd xs ) • odd – стандартная функция • countOdd1 xs = sum (map (`mod` 2) xs) Еще варианты (карринг, комбинаторное программирование): • countOdd1 = length . filter odd • countOdd1 = sum . map (`mod` 2)
myFoldl foldl (+)0 [1,2,3] = (((0+ 1) + 2) + 3) • Свести к задаче для [2,3] (((0 + 1) + 2) + 3) e1 = 0 + 1 ((1 + 2) + 3) foldl (+) 1 [2,3] • Т.е.выразили foldl через foldl от хвоста списка foldl f e (x:xs) = let e1 = e `f` x in foldl f e1 xs • или, короче, foldl f e (x:xs) = foldl f (e `f` x) xs foldl f e [] = e Замечания: • Аргументы f в другом порядке, чем в foldr • Что можно сказать про foldl? • Хвостовая рекурсия • e – накапливающий параметр
fromIntegral x = sqrt (mod 100 7) • Ошибка – тип Integer и Double x = sqrt (fromIntegral (mod 100 7)) • fromIntegral – преобразует целое в вещественное
Самая выразительная функция? • map • filter • foldr и foldl • Все можно выразить через… • foldr!
Как еще можно понимать foldr Способ 3 – заменяем в дереве внутреннего представления • Заменяем [] на e • Заменяем : на f • Вычисляем то, что получится
Статичесткое/динамическое связывание
Что тут получится? a = 5-- Определяем a res = let f x = x * a in let a = 7 -- Переопределяем a in f 2 Что получится? • 2*5 = 10 • 2*7 = 14 • 2*5 = 10 • значение a запоминается при определении функции • статическое связывание • 2*7 = 14 • значение a берется при вызове функции • динамическое связывание • Haskell – статическое связывание • ср. referential transparency • f 2всегда должен возвращать одно и то же
Замыкание • Снова checkDifferent из д.з. 1 checkDifferent (x:xs) = let f t = t == x in if any f xs then False else checkDifferent xs • f – не совсем обычная функция • Мы не можем определить ее внеlet. Почему? • Потому что она содержит переменную x, не локальную в f, но локальную в внешней функции • Другими словами: f содержит переменную,глобальную для f, но локальную для checkDifferent • Это называется замыкание(closure)
Почему это важно? Воображаемый диалог: • Программист A: любитель языка С • Программист B: любитель Haskell (или C# и т.д.) • B: Мы можем определять функции, у которых параметры – другие функции! Например, any. A: Ну и что, я тоже могу написать что-то типа:bool any(int arr[], bool (*f)(int)){ …} • Как убедить, что Хаскел лучше? Задача: написать функцию checkDigit: «Проверить, есть ли в списке / массиве число, оканчивающиеся на d?» Используя any
Почему это важно - продолжение • B: checkDigit d xs = let f x = mod x 10 == d in any f xs • A: bool any(int a[], bool (*f)) { … как-то написал … } … • Но непонятно, что передать в any! boolcheckDigit(int a[], int d) { ??? Как определить f ? ??? Похоже, никак any(a, f); } • Мораль: Без замыканий использовать функции высшего порядка не очень удобно
Замечания • Разные определения: • Замыкание – это функция, в которой есть нелокальные переменные • Структура, где все это хранится • В С++ это capture list any_of(v.begin(), v.end(), [d] (int i) { return i % 10 == d; });
List comprehension(генераторы списков?) • примерно как в математике описываются множества S = { x: x R, x > 5 } Примеры: • Удобная запись для map [sin x | x <- [1..n]] [sin 1, sin 2, … , sin n] • Удобная запись для filter [x | x <- xs, x > 0] • Отобрать положительные числа • Можно сразу и map и filter [sin x | x <- xs, x > 0] • Отобрать положительные числаи сгенерировать список из их синусов
List comprehension – еще возможности • Декартово произведение [x*y | x<-[1..n], y<-[1..n]] • Список всех попарных произведений • Это уже больше, чем удобная запись для map и filter! Еще пример: [(x,y) | x<-[1..n], y<-[1..n], x^2+y^2 < n^2] • Четверть круга • let переменная = выражение [(x,y) | x<-[1..n], let xx = x*x, y<-[1..n], xx+y^2 < n^2]
Что можно писать в list comprehension? [ выражение | конструкция1, конструкция2, …] • Конструкции: • генератор (generator)выражение <-список x <- [1..n] • охраняющее выражение (guard)логическое выражение x*x+y*y <= 100 • let конструкцияlet выражение1= выражение2 let x2 = x*x
C# (для следующей задачи на ‘обычном’ языке
Аналоги map и filter • Where – аналог filter • Select – аналог map int [] a = { 1, 2, 3, 4, 5 }; double [] b = a.Select(x => Math.Sin(x)).ToArray(); // Теперь в массиве b синусы всех чисел из массива a double [] c = b.Where(x => x > 0).ToArray(); // Теперь в массиве с все положительные числа из массива b // А можно сразу применить и Select и Where double [] c1 = a.Select(x => Math.Sin(x)). Where(x => x>0).ToArray(); • Или можно использовать LINQ query expressions (выражения запросов)
nseq nseq n = nseqFrom 1 n nseqFrom k n = if k*2 >= n then 1 else nseqFrom (k+1) (n-k) + nseqFrom (k+1) n • Тест map [1..100] nseq Как сосчитать быстрее? • Динамическое программирование! • nseqFrom k n – количество последовательностей, в которых все числа >= n • nseqFrom 2 16 / \ Содержит 2Не содержит 2 23 4 73 4 9 23 5 6 3 5 8 2 3 11 3 6 7 2 4 10 3 13 2 5 9 4 5 7 … … nseqFrom3 14+nseqFrom3 16
Integral static double Integral(Func<double, double> f, double a, double b) { double sum = 0; var h = (b - a)/100; for (var x = a + h/2; x < b; x += h) sum += f(x)*h; return sum; } • Пример вызова: var result = Integral(x => x*x, 0, 2); • Замечание: • sum += (f(x)+f(x+h))/2 • Два вызова f в цикле – неэффективно… • В функциях высшего порядка важно вызыватьне вызывать параметры лишний раз!
Иллюстрация к задаче 4 • 5 коп • 3 коп • 2 коп