Модул 11. Функционално програмиране
Материали | Задачи | Решения | Видео
Съдържание
Въведение
Функции и стойности
Рекурсия
Списъци
Функции от по висок ред
Затваряне на състояние във функция
Подготовка за изпит
1. Въведение
Инсталация
Първа програма
VS Code > File > New [Ctrl + N]
-- First Haskell Program
main = do
putStrLn "Hello World"
VS Code > File > Save [Ctrl + S]
hello.hs
VS Code > Terminal > New terminal [Ctrl + Shift + `]
cd Desktop
ghc hello.hs
.\hello.hs
2. Функции и стойности
Деклариране на функция
square x = x * x
Функция с повече параметри
multMax a b x = (max a b) * x
Използване на скоби
(5 + 2) * (3 - 4) -- -7
max (5 + 2) (sqrt 17) -- 7
max 5 (-5) -- 5
Функции като стойности на функция
pass3 f = f 3
add1 x = x + 1
pass3 add1
-- 4
Композиция
compose f g x = f (g x)
add1 x = x + 1
mult2 x = 2 * x
compose add1 mult2 4
Рекурсия
pow2 n =
if n == 0
then 1
else 2 * (pow2 (n - 1))
3. Рекурсия
В Haskell няма цикли. Циклите се реализират чрез рекурсия. Итерациите се реализират с рекурсивни извиквания. Итераторите се реализират като параметри и се променят при всяко рекурсивно извикване.
Рекурсивна реализация на цикли
repeatString str n =
if n == 0
then ""
else str ++ (repeatString str (n-1))
За функциите, които зависят от външно състояние се използва помощна функция:
pow2loop n x i =
if i < n
then pow2loop n (x*2) (i+1)
else x
pow2 n = pow2loop n 1 0
Задача
Дефинирайте функция, която намира сбора на първите 10 естествени числа.
sumNumbers = sumNumbersLoop 0 1
sumNumbersLoop sum index =
if index > 10
then sum
else (sum + index) + (sumNumbersLoop sum (index + 1))
Опашкова рекурсия
Опашковата рекурсия е рекурсия, при която последното извършвано действие е рекурсивно извикване.
Оптимизация на опашката = Tail Call Optimization
Вместо с последващо връщане рекурсивното обръщение се реализира със преход без връщане
При тази рекурсия заделената в стека памет се преизползва вместо да се заделя нова
Намалява разхода на памет и обикновено подобрява бързината на алгоритъма, но по-трудно се откриват грешки
repeatStringLoop string result n =
if n == 0
then result
else repeatStringLoop string (result ++ string) (n - 1)
repeatString string n = repeatStringLoop string string n
4. Списъци
Инициализация на списък
x = [1,2,3]
Празен списък
empty = []
Операторът :
y = 0 : x -- [0,1,2,3]
x' = 1 : (2 : (3: [])) -- [1,2,3]
Символните низове също са списъци
str = "abcde"
str' = 'a' : 'b' : 'c' : 'd' : 'e' : []
Глава и опашка на списъци
Глава на списъка е първият елемент от него
head[1, 2, 3] -- 1
Опашка на списъка е всичко останало освен главата
tail [1,2,3] -- [2, 3]
В комбинация от функциите можем да достъпим следващия елемент от списъка
head (tail [1,2,3]) -- 2
Рекурсивно обхождане на списък
Рекурсивно обхождане на списък и умножаване на всяка стойност по 2:
doubleList list =
if null list
then []
else (2 * (head list) : (doubleList (tail list)))
doubleList [1,2,3,4,5] -- [2,4,6,8,10]
Функция, филтрираща елементите в списък (премахва нечетни числа):
removeOdd nums =
if null nums
then []
else
if (mod (head nums) 2 ) == 0
then (head nums) : (removeOdd (tail nums))
else removeOdd (tail nums)
Дължина на списък
За намиране на дължината на списък се използва функцията length
length [1,2,3,4,5] -- 5
Собствена функция за намиране на дължината
listLength [] = 0
listLength list = findLength 1 list
findLength length list =
if null list
then (length - 1)
else findLength (length + 1) (tail list)
Създаване на списък чрез рекурсия
Създаване на списък чрез рекурсия:
createList start end = createListLoop [] start end
createListLoop list start end =
if start > end
then list
else createListLoop (list ++ [start]) (start + 1) end
createList 1 10 -- [1,2,3,4,5,6,7,8,9,10]
Създаване на обърнат списък чрез рекурсия:
createReverseList start end = createReverseListLoop [] start end
createReverseListLoop list start end =
if start > end
then list
else createReverseListLoop (start : list) (start + 1) end
createReverseList 1 10 -- [10,9,8,7,6,5,4,3,2,1]
В Haskell създаването на списък може да продължава до безкрайност:
intsFrom n = n : (intsFrom (n+1))
ints = intsFrom 1 -- Продължава до безкрайност
take 10 ints -- [1,2,3,4,5,6,7,8,9,10]
Задача
Дефинирайте функция, която приема списък и число n и връща като резултат n-тия елемент от списъка
nThElement list n = nThElementLoop list (length list) n 0
nThElement [] _ = error "Empty list"
nThElementLoop list listLength n index =
if n >= listLength || n < 0
then error "Index outside bounds of array"
else if index == n
then (head list)
else nThElementLoop (tail list) listLength n (index + 1)
5. Функции от по-висок ред
Абстракции чрез функции
Ако функция a приема като параметри b, c и друга функция func и връща резултат извиканата функция func с параметри b и c, то резултатът всеки път ще е различен
Резултатът зависи от подадената функция func, като единственото условие е тя да приема същия брой параметри, които и се подават в тялото на a
abstThroughFunction a b func = func a b
firstFunc a b = (a * b)
secondFunc a b = (a + b)
thirdFunc a b = (a - b)
abstThroughFunction 10 10 firstFunc -- 100
abstThroughFunction 10 10 secondFunc -- 20
abstThroughFunction 10 10 thirdFunc -- 0
Изчисления върху списъци
Функцията map приема като параметри функция и списък и връща като резултат нов списък, като върху един елемент от първоначалният списък е извикана подадената функция.
absoluteList list = map abs list
absoluteList [1,2,-3,-4] -- [1,2,3,4]
plus1List list = map (1 + ) list
plus1List [1,2,3,4,5] -- [2,3,4,5,6]
Функцията filter тества всеки елемент от списък и връща само тези, които минават теста (функция, която връща тип boolean)
isEven x = x `mod` 2 == 0
removeOdd = filter isEven
removeOdd [1,2,3,4,5,6,7,8] -- [2,4,6,8]
Сгъване на списък е комбиниране на всички стойности от списъка в една. Има две вградени функции, които правят това:
Функцията foldl = действията се извършват от ляво надясно
Функцията foldr = действията се извършват от дясно наляво
И двете функции приемат три параметъра:
Акумулатор - функцията, която ще се извиква между елементите на списъка
Начална стойност, от която да започне изчислението
Самият списък
И при двете функции изчисленията започват от стойността на акумулатора:
foldl е по-бърза функция
foldr намира приложението си при работа с безкрайни списъци
subtractList list = foldl (-) 0 list
subtractList' list = foldr (-) 0 list
subtractList [1,2,3,4,5] -- -15
subtractList' [1,2,3,4,5] -- 3
Функцията zip приема като аргументи два списъка и връща като резултат списък от двойки, където първият елемент е от първият списък, а другият от вторият списък.
zip [1,3,5] [2,4,6] -- [(1,2),(3,4),(5,6)]
zip [1,2] [3,4,5,6] -- [(1,3),(2,4)]
zip [] [1] -- []
Функцията zipWith освен два списъка приема и функция, която да използва при комбинирането на елементи от двата списъка.
zipWith (+) [1,2,3,4,5] [9,8,7,6,5] -- [10,10,10,10,10]
Задача
Дефинирайте функция, която приема лист и връща най-големият елемент от нея. Използвайте някоя от научените функции за изчисления върху списък.
maxFromList list = foldl max (head list) list
maxFromList [-1, 5, 10] -- 10
Анонимни функции
Следният синтаксис често обърква и прави кода нечетим
plus3 x y z = x + y + z
В такива случаи много удобни за използване са анонимните функции
(\x y z -> x + y + z)
(\x y z -> x + y + z) 10 20 30 -- 60
В Haskell е възможно и да се дават имена на анонимни функции, ако по някаква причина това е нужно
plus3' = (\ x y z -> x + y + z)
Анонимните функции имат огромно приложение при използването на вградените в Haskell функции map и foldl/foldr при работа със списъци
addOneList list = map (\x -> x + 1) list
addOneList [1,1,1] -- [2,2,2]
Задача
Използвайте вградената в Haskell функция zipWith, като за първи параметър (функция) използвате ваша анонимна функция, която връща сбора на два елемента.
zipWith (\ x y -> x + y ) [10,12] [3,4] -- [13,16]
6. Затваряне на състояние във функция
В Haskell съществуват функции с така наречените "свободни променливи" (променливи, които не са директно подадени като параметър на функцията)
Функциите със свободни променливи наричаме функции с вътрешно състояние
Haskell използва функции със свободни променливи почти навсякъде т.е. функциите с вътрешно състояние се използват навсякъде
Пример
f x = (\y -> x + y)
f връща функция с вътрешно състояние, защото променливата x, която е подадена отвън за анонимната функция, се използва вътре в дефиницията ѝ
Функциите с вътрешно състояние са обратния случай на комбинаторите - функции без свободни променливи.
Функция без свободни променливи е чиста анонимна функция, която се обръща само към своите аргументи.
\a -> a
\a -> \b -> a
\f -> \a -> \b -> f b a
7. Подготовка за изпит
Задaчa. 1 Цифра като дума
Напишете програма, която продължително приема едноцифрени числа за вход и ги принтира на конзолата като дума.
0
Zero
1
One
2
Two
3
Three
4
Four
5
Five
6
Six
7
Seven
8
Eight
9
Nine
При въвеждане на End програмата да спира изпълнението си. За целта - възползвайте се от библиотеката System.Exit и метода, който тя предлага exitWith, като го извикате с параметър ExitSuccess. При вход на отрицателно число или число с повече от 1 цифра да се изпише: Please only enter single digit positive numbers.
Пример
5
Five
9
Nine
11
Please only enter single digit positive numbers
End
Задача 2. Средно аритметично на цифрите на число
Напишете програма, която приема на стандартният вход цяло число и отпечатва на стандартният изход средното аритметично на неговите цифри.
На стандартният вход се въвежда цяло число N
На стандартният изход се извежда цяло число - средното аритметично на цифрите на N
Ограниения
1 <= N <= 10000
Пример
2222
2
2345
3
Задача 3. Най-малка цифра в число
Да се състави прогрма, която намира и отпечатва най-малката цифра в цяло число.
На стандартният вход се въвежда цяло число N
На стандартният изход се отпечатва цяло число M - най-малката цифра в N
Ограничения
1 <= N <= 10000
Пример
6798154
1
Last updated
Was this helpful?