Az if...then...else kifejezésre már korábban is láttunk példát, most azonban azt is ki szeretnénk hangsúlyozni, hogy az if a Haskellben nem utasítás vagy állítás, hanem egy feltételes kifejezés, ezért az else ág kötelező.
4.9. feladat Írjunk egy Haskell-függvényt, amely megvizsgálja, hogy x osztható-e y-nal.
oszthato :: (Integral a) => a -> a -> Bool
oszthato x y = if mod x y == 0 then True else False
4.6. Feltételes kifejezések 51
> oszthato 21 7 True
A korábban megírt faktoriális függvényt is lehet feltételes kifejezésekkel definiálni:
fakt :: (Eq a, Num a) => a -> a
fakt n = if n == 0 then 1 else n * fakt (n-1)
> fakt 100
9332621544394415268169923885626670049071596826438...
Többágú kifejezések megadására a case...ofkifejezés alkalmazható, amelynek használatát egy feladaton keresztül mutatjuk be, ahol szükségünk lesz a chrfüggvényre is. Ahhoz, hogy tudjuk ezt használni, importálni kell aData.Charkönyvtárcsomagot. Achrfüggvény meghatározza azt az Uni-code kódolás szerinti szimbólumot, amely a paraméterként megadott egész számhoz tartozik.
4.10. feladat Írjuk meg a hexaSz Haskell-függvényt, amely meghatározza azt a 16-os számrendszerben használt szimbólumot, amelyet egy 0 és 15 közötti számjegyhez rendelnek hozzá. A kiíratást az A, B, C, D, E, F szimbólumok segítségével végezzük.
import Data.Char hexaSz :: Int -> Char hexaSz c
| c >= 0 && c < 16 = case c of
10 -> 'A' 11 -> 'B' 12 -> 'C' 13 -> 'D' 14 -> 'E' 15 -> 'F'
_ -> chr (c + 48)
| otherwise = error "hibas bemenet"
> hexaSz 15 'F'
> hexaSz 19
*** Exception: hibas bemenet...
A fenti kódsorban acasehét esetet tárgyal aszerint, hogy acértéke mennyi.
Az utolsó eset, azaz acaseutolsó sora azt az esetet kezeli, amikor a koráb-bi feltételek közül egyik sem teljesült. A _ szimbólum mindenes operátor, szerepe az, hogy olyan helyzeteket kezeljen, amikor nem számít a bemenet vagy a mintázott érték. Használatával a fenti függvényben a minden más esetben adjuk meg a függvény kimeneti értékét, azaz ha a c nem egyen-lő 10, 11, 12, 13, 14, 15-tel, akkor a chr (c + 48) kifejezés kerül kiértékelésre. Mivel a '0' karakter kódja 48, a chr (c + 48) kifejezés meghatározza a cszámjegy karakter-értékét.
4.11. feladat Írjunk egy Haskell-függvényt, amely meghatározza egy16-os számrendszerben megadott számsorozatnak azon alakját, amelyben a szám-értékeket a16-os számrendszerben használt szimbólumokkal helyettesítjük.
Használjuk a korábban megírt hexaSzfüggvényt.
hexaLs :: [Int] -> [Char]
hexaLs [] = ""
hexaLs (k : ve) = hexaSz k : hexaLs ve
> hexaLs [12, 4, 5, 15, 7, 0, 11, 4]
"C45F70B4"
> hexaLs [12, 4, 5, 17, 7, 0, 11, 4]
*** Exception: hibas bemenet...
A függvénytörzs mintaillesztés alapján választja szét a triviális esetet az ál-talánostól, azaz amikor a bemenet üres lista, illetve amikor nem. Ez utóbbi esetben a : operátor segítségével választottuk le a lista első elemét, a k-t a lista többi részétől, a ve-től. Az egyenlőség jobb oldalán a k, illetve ve étékekkel így műveleteket tudunk végezni. Ez jelen esetben azt jelenti, hogy a bemeneti egész számokat tartalmazó lista alapján felépítünk egy új lis-tát. Az új listát úgy építjük fel, hogy alkalmazzuk a hexaSz függvényt a bemeneti lista első elemén, ak-n, majd ezt az értéket a :operátor segítsé-gével a rekurzív hívás során nyert lista elé fűzzük. Az újonnan épített lista lesz a függvény kimenetének értéke. Figyeljük meg a:operátor szerepét az egyenlőség bal, illetve jobb oldalán, a későbbiekben még visszatérünk rá.
4.7. Halmazkifejezések 53
4.7. Halmazkifejezések
Halmazkifejezéseket vagy listagenerátorokat iteratív adatszerkezetek (listák, halmazok, sorozatok) elemeinek megadásakor alkalmazunk. Az an-gol terminológia erre a list comprehension megnevezést használja. Ezzel a jelölésrendszerrel nagyon egyszerűen lehet listaelemeket megadni, generál-ni, feldolgozni. A legegyszerűbb feladatok egy lista elemeiből kiindulva azon listaelemekből hoznak létre új listát, amelyek eleget tesznek egy adott fel-tételnek.
4.12. feladat Írjunk egy Haskell-függvényt, amely meghatározza a paramé-terként megadott szám osztóit.
osztok :: Integral a => a -> [a]
osztok n = [i | i <- [1..n], mod n i == 0]
> osztok 60
[1,2,3,4,5,6,10,12,15,20,30,60]
A kódsorban azokból az i értékekből hozzuk létre az eredménylistát, ame-lyek az[1..n]listának elemei, és eleget tesznek a mod n i == 0 feltétel-nek.Halmazkifejezések megadásakor egy adott lista elemei alapján hozunk létre egy új listát, ahol az új listaelemekre vonatkozó szabályokat szögletes zárójelben kell megadni. A szögletes zárójelbe írtak két részre oszthatók, a | jel előtti részre, illetve az utána következő részre. Az első részben az új lista elemeit vagy a listaelemekre vonatkozó kifejezéseket adhatunk meg.
A második részben a <- szimbólum segítségével a listaelemek generálási módját határozzuk meg. A,utáni rész nem kötelező, ide logikai kifejezések, megszorítások írhatók.
4.13. feladat Írjunk egy Haskell-függvényt, amely meghatározza egy16-os számrendszerben megadott számsorozatnak azon alakját, ahol a számér-tékeket a 16-os számrendszerben használt szimbólumokkal helyettesítjük.
Használjuk a korábban megírt hexaSzfüggvényt.
hexaLc :: [Int] -> [Char]
hexaLc ls = [hexaSz k | k <- ls]
> hexaLc [12, 4, 5, 15, 7, 0, 11, 4]
"C45F70B4"
A feladatot korábban is megoldottuk, most halmazkifejezéssel azonban egy egyszerűbb kódsort tudtunk írni. Fontos ugyanakkor leszögezni, hogy haté-konyság szempontjából ez a módszer nem lesz jobb.
4.14. feladat Írjunk egy Haskell-függvényt, amely egy olyan háromelemű tuple-ökből álló listát generál, ahol a tuple-elemek a 0, 1,...,n összes lehetséges értékeit felveszik, az összes lehetséges módon.
tupleLc :: (Num a, Enum a) => a -> [(a, a, a)]
tupleLc n = [(x, y, z) | x <- [0..n], y <- [0..n], z <- [0..n]]
> tupleLc 1
[(0,0,0),(0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1), (1,1,0),(1,1,1)]
4.15. feladat Írjunk egy Haskell-függvényt, amely meghatározza adott n-ig a pitagoraszi számhármasokat, ahol az x, y, z három természetes szám, pitagoraszi számhármast alkot, ha fennáll:x2 == y2 + z2.
A következő két függvényben kétféleképpen is megadjuk a generálási szabályt, illetve a feltételeket, ahol a második módszer lesz a hatékonyabb.
pitagorasz :: (Num a, Enum a, Ord a) => a -> [(a, a, a)]
pitagorasz n = [(x, y, z) | x <- [1..n], y <- [1..n], z <- [1..n], x * x == y * y + z * z, y < z]
pitagorasz_ :: (Num a, Enum a, Eq a) => a -> [(a, a, a)]
pitagorasz_ n = [(x, y, z) | x <- [1..n], y <- [1..n], z <- [y + 1..n], x * x == y * y + z * z]
> pitagorasz 17
[(5,3,4),(10,6,8),(13,5,12),(15,9,12),(17,8,15)]
4.16. feladat Írjunk egy Haskell-függvényt, amely kiválasztja egy listából a négyzetszámokat.
valasztN :: Integral a => [a] -> [a]
valasztN ls = [i | i <- ls, negyzetV i]
negyzetV :: Integral a => a -> Bool negyzetV x = temp * temp == x
where
4.7. Halmazkifejezések 55
temp = truncate (sqrt (fromIntegral x))
> valasztN [12, 144, 200, 196, 154, 9, 8, 4, 6, 100, 625]
[144,196,9,4,100,625]
A négyzetszámok vizsgálatát anegyzetVfüggvény végzi, amely előbb négy-zetgyököt számol a bemeneti paraméteren, majd a truncate függvénnyel levágja a tizedesjegyeket, és az így kapott szám négyzetéről vizsgálja meg, hogy egyenlő-e a bemenettel. Azsqrtbemenete FloatvagyDouble típu-sú, ezért a fromIntegralfüggvénnyel típuskonverziót hajtottunk végre.
4.17. feladat Írjunk egy Haskell-függvényt, amely a bemeneti lista elemeit kétfelé válogatja, meghatározza egy listába a prímszámokat, egy másikba pedig az összetett számokat.
import Data.List
valogatNr :: (Integral a) => [a] -> ([a], [a]) valogatNr ls = (primL, osszL)
where
primL = [i | i <- ls, primT 3 i]
osszL = ls \\ primL
> valogatNr [24, 97, 5, 11, 74, 41, 61, 19, 100]
([97,5,11,41,61,19],[24,74,100])
A kért listákat előállító főfüggvény a valogatNr lesz, amelyben a primT segítségével határoztuk meg a prímszámokat. AprimT-nek két pa-ramétere van, a másodikról vizsgálja, hogy prímszám-e úgy, hogy az első paraméterrel való oszthatóságot figyeli.
primT :: (Integral a) => a -> a -> Bool primT k nr
| nr <= 1 = error "hibas bemenet"
| nr == 2 = True
| even nr = False
| nr < k * k = True
| mod nr k == 0 = False
| otherwise = primT (k + 2) nr
> primT 3 1789 True
> primT 3 100 False
AprimTelső három feltétele a triviális eseteket kezeli. Azeven könyv-tárfüggvény, amelynekTruea visszatérési értéke, ha a bemenet páros szám, ellenkező esetbenFalse. A negyedik feltétel akkor teljesül, ha a szám négy-zetgyökéig nem találtunk páratlan osztót, ebben az esetben True lesz a kimenet értéke, mert a szám prímszám. A négyzetgyök vizsgálata nem exp-licit módon történik, hanem helyette megvizsgáljuk, hogy az osztó négyzete nem nagyobb-e a számnál. Az ötödik feltételben kerül sor az oszthatóság vizsgálatára, amely ha fennáll, akkor Falselesz a függvény kimeneti érté-ke, mert a szám összetett. Azotherwiseágban a rekurzív függvényhívásra kerül sor. Itt figyeljük meg, hogy akértékét kettesével léptetjük, mert a ha-tékonyság miatt az oszthatóságot csak páratlan számokra teszteljük, a páros bemenetet ugyanis egy korábbi feltételben kezeltük. Ahhoz, hogy páratlan számokkal végezhessük az oszthatóságot, a primTfüggvény első paraméte-rének kezdőértékét3-ra állítottuk.
AvalogatNrfüggvényben aprimLlistába azokat a számokat tesszük, amelyek prímszámok. AzosszLaz összetett számok listáját jelöli, ahol két lista különbségének a meghatározásához az \\operátort használtuk.
A \\operátor aData.Listkönyvtármodulban van, tetszőleges elem-típusú listákon alkalmazható. Az eredeti listában a megadott elemek első előfordulását törli, meghagyva a többi előfordulási értéket. Figyeljük meg, hogyan kell eljárni, ha egy könyvtárcsomagból csak egy függvényt szeret-nénk használni. A következő lekérdezések előtt a \\ használatához nem importáljuk a teljes könyvtárcsomagot.
> import Data.List ( (\\) )
> [45, 7, 8, 12, 3, 9, 10] \\ [45, 10, 12]
[7,8,3,9]
> [45, 7, 8, 12, 45, 3, 45, 9, 10] \\ [45, 10, 12]
[7,8,45,3,45,9]
> "Nyugati-Karpatok" \\ "aeiou"
"Nygt-Karpatk"