Hajtogatások

Hajtogatások

Észrevehetjük, hogy a bizonyos rekurzív, listafeldolgozó függvényeink jellemző hasonlóságot mutatnak egymáshoz. Ezt a közös, sémaként kiemelhető részt ún. hajtogatások (angolul folding) segítségével írhatjuk le. A hajtogatás lényege, hogy egy listából egy binér függvény segítségével valamilyen eredményt ,,hajtogatunk’’. Ekkor lépésenként összekombináljuk a lista elemeit a függvény által kiszámított eredménnyel.

Feladat: Prelude.foldr [*]

Definiáljuk újra rekurzióval a jobbról hajtogatás műveletét!

foldr :: (a -> b -> b) -> b -> [a] -> b

Test>
"hajtogatnivaló" :: [Char]
Test>
210 :: Integer

Megjegyzés. Létezik balról hajtogatás is, ez a foldl függvény.

foldl :: (b -> a -> b) -> b -> [a] -> b

A kettő közti különbség egy példán keresztül:

Test>
"(1+(2+(3+(4+(5+(6+(7+(8+(9+(10+0))))))))))" :: [Char]
Test>
"((((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9)+10)" :: [Char]

Bizonyos esetekben ezzel kényelmesebb felírni a megoldást. Fontos különbség azonban, hogy a foldr képes végtelen listákkal is dolgozni, míg a foldl nem.

A különbség megértéséhez adjuk meg a foldl definícióját is:

foldl :: (b -> a -> b) -> b -> [a] -> b

Test>
"ólavintagotjah" :: [Char]
Test>
210 :: Integer

Feladat: Prelude.sum [*]

Definiáljuk újra a sum függvényt!

sum :: Num a => [a]{-véges-} -> a

Test>
55 :: Integer
Test>
0 :: Integer

Feladat: Prelude.product

Definiáljuk újra a product függvényt!

product :: Num a => [a]{-véges-} -> a

Test>
24 :: Integer
Test>
1 :: Integer

Feladat: Prelude.and

Definiáljuk újra az and függvényt!

and :: [Bool]{-véges-} -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
True :: Bool

Feladat: Prelude.or

Definiáljuk a logikai VAGY függvényt!

or :: [Bool]{-véges-} -> Bool

Test>
True :: Bool
Test>
False :: Bool
Test>
False :: Bool

Feladat: Beszúrásos rendezés

Definiáljuk a beszúrásos rendezést!

isort :: Ord a => [a] -> [a]

Test>
[1, 2, 3, 4, 6, 11] :: [Integer]

Feladat: Prelude.concat

Definiáljuk újra a concat függvényt!

concat :: [[a]] -> [a]

Test>
"abbc" :: [Char]
Test>
True :: Bool

Feladat: (Prelude.++)

Definiáljuk újra a (++) függvényt!

(++) :: [a] -> [a] -> [a]

Test>
[1, 2, 3, 4, 5, 7, 8, 9, 10] :: [Integer]
Test>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] :: [Integer]

Feladat: Prelude.length [*]

Definiáljuk újra a length függvényt!

length :: [a]{-véges-} -> Int

Test>
10 :: Int
Test>
0 :: Int

Feladat: Prelude.reverse

Definiáljuk újra a reverse függvényt!

reverse :: [a]{-véges-} -> [a]

Test>
"amla" :: [Char]

Feladat: Prelude.maximum

Találjuk meg több elem maximumát!

maximum :: Ord a => [a]{-véges, nemüres-} -> a

Test>
4 :: Integer

Feladat: Bináris szám értéke

Írjuk meg azt a függvényt, amely egy bináris számjegy-sorozatból kiszámítja annak értékét!

fromBin :: [Int] -> Integer

Test>
0 :: Integer
Test>
1 :: Integer
Test>
2 :: Integer
Test>
10 :: Integer

Feladat: Polinom kiértékelése

Értékeljünk ki adott helyen egy együtthatóival megadott polinomot!

polinom :: Num a => [a] -> a -> a

Test>
487 :: Integer

Hajtogatások kezdőérték nélkül

A hajtogatásoknál nem kötelező megadni a kezdőértéket. Létezik olyan változat, ahol a kezdőérték automatikusan a lista első eleme lesz. Ekkor viszont (értelemszerűen) a lista nem lehet üres!

Típusok:

foldl1 :: Foldable t => (a -> a -> a) -> t a -> a
foldr1 :: Foldable t => (a -> a -> a) -> t a -> a

Feladat: Prelude.minimum [*]

Definiáljuk újra a minimum függvényt!

minimum :: Ord a => [a]{-véges, nemüres-} -> a

Test>
1 :: Integer

Hajtogatások részeredményekkel

A hajtogatások másik lehetséges fajtája az, amikor a keletkező részeredményeket összegyűjtjük egy listába és ezt adjuk vissza. Ez pásztázásnak (vagy angolul scanning) nevezzük.

Feladat: Prelude.scanr [*]

Definiáljuk újra rekurzióval a jobbról pásztázás műveletét!

scanr :: (a -> b -> b) -> b -> [a] -> [b]

Test>
["pásztáznivaló", "ásztáznivaló", "sztáznivaló", "ztáznivaló", "táznivaló", "áznivaló", "znivaló", "nivaló", "ivaló", "való", "aló", "ló", "ó", []] :: [[Char]]
Test>
[55, 54, 52, 49, 45, 40, 34, 27, 19, 10, 0] :: [Integer]

A hajtogatás és pásztázás kapcsolatát az alábbi állítás írja le:

head (scanr f z xs) == foldr f z xs

Megjegyzés. Létezik balról pásztázás is, ez a scanl függvény.

scanl :: (b -> a -> b) -> b -> [a] -> [b]

Feladat: Prelude.scanl

Definiáljuk újra rekurzióval a balról pásztázás műveletét!

scanl :: (b -> a -> b) -> b -> [a] -> [b]

Test>
[[], "p", "áp", "sáp", "zsáp", "tzsáp", "átzsáp", "zátzsáp", "nzátzsáp", "inzátzsáp", "vinzátzsáp", "avinzátzsáp", "lavinzátzsáp", "ólavinzátzsáp"] :: [[Char]]
Test>
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55] :: [Integer]

Ebben az esetben a következő összefüggés érvényes:

last (scanl f z xs) == foldl f z xs

Kezdőérték nélküli változatok:

scanl1 :: (a -> a -> a) -> [a] -> [a]
scanr1 :: (a -> a -> a) -> [a] -> [a]

Feladat: Összegsorozat

sums :: [Integer] -> [Integer]

Test>
[1, 2, 3, 4, 5] :: [Integer]
Test>
[1, 4, 9, 16, 25] :: [Integer]

Feladat: Fibonacci sorozat

fibs :: [Integer]

Test>
[1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657] :: [Integer]

Segítség. Figyeljük meg:

Test>
[1, 1, 2, 3, 5, 8, 13, 21] :: [Integer]

Feladat: Növekvő maximumok sorozata

increasingMaximums :: Ord a => [a] -> [a]

Test>
[1, 3, 8] :: [Integer]