• Nem Talált Eredményt

A standard bemenet és kimenet

Haskell-listák

6.3. A standard bemenet és kimenet

A következőkben példákon keresztül mutatjuk be, hogy a Haskellben hogyan használjuk a standard bemenetet és kimenetet.

6.4. feladat Írjunk egy Haskell-függvényt, amely meghatározza a billentyű-zetről beolvasottn szám páros osztóinak listáját.

foParosO :: IO () foParosO = do

putStr "n = "

temp <- getLine

let n = read temp :: Int

let res = [i | i <- [2, 4 .. n], mod n i == 0]

print res

> foParosO n: 60

[2,4,6,10,12,20,30,60]

A foParosO függvénynek nincs bemeneti paramétere, kimeneti értékének típusa IO (). A getLinefüggvény, ahogy ezt már megfigyelhettük, a bil-lentyűzetről történő adatbevitelt teszi lehetővé, típusdeklarációjából jól lát-szik, hogy nem vár bemeneti értéket, kimenetének típusa pedigIO String, azaz egy IO monád lesz, ami egy olyan számítást definiál, ahol a végered-mény típusaString. Hasonlóan aputStrés aprintis egy-egyIOmonád, amelyek olyan számításokat definiálnak, ahol a végeredmény típusa (), a függvények kimenetének típusa pedig IO ().

getLine :: IO String putStr :: String -> IO () print :: Show a => a -> IO ()

AfoParosOfüggvény törzse egydoblokk keretén belül több, összesen öt akció-nak nevezett műveletet hajt végre, abban a sorrendben, ahogyan megadtuk. Szerepük a következő:

1. aputStrfüggvénnyel kiírunk a képernyőre,

6.3. A standard bemenet és kimenet 131 2. agetLinefüggvénnyel adatot olvasunk be billentyűzetről, amelyet

a <-használatával kicsomagolunk éstemp-pel jelölünk,

3. a billentyűzetről beolvasott String típusú értéket átalakítjuk Int típussá, amelyet a let ... =jelölést használvan-el jelölünk, 4. a kért lista előállításához halmazműveletet használunk, az eredményt

a res-sel jelöljük, megint alet ... =jelölést használjuk, 5. aprintfüggvénnyel kiírunk a képernyőre.

A fenti akciók közül a harmadik és negyedik akció olyan függvények kiér-tékelését valósítják meg, amelyeknek nincs mellékhatásuk, ezért használjuk a let ... =jelölést, azaz jelezzük, hogy tiszta funkcionális programozási stílusban megírt függvényeket kell kiértékelni. A Haskell tehát külön jelölési rendszert dolgozott ki arra, amikor egy tiszta függvényt akar kiértékelni, és mást használ, amikor egy mellékhatással járó függvényt hajt végre. Élesen elkülöníti a tisztán funkcionális paradigmában írt függvényeket a mellékha-tással járó függvényektől.

A következő függvény egy karakterláncot olvas be a billentyűzetről, majd meghatározza azt a két karakterláncot, amelyben csak a kis- és nagy-betűk, illetve azt, amelyben csak a számjegyek szerepelnek. Figyeljük meg, hogy a return használatakor nem következik be az olvasStr_-ből való kilépés.

import Data.Char (isAlpha, isNumber) olvasStr_ :: IO ()

olvasStr_ = do

putStr "kerek egy karakterlancot: "

str <- getLine

x <- return (filter isAlpha str) y <- return (filter isNumber str)

putStrLn $ "Az alfanumerikus karakterek: " ++ x putStrLn $ "A szamok: " ++ y

A következő függvény ugyanazt végzi, mint az előző, csak az implemen-táció során a filter által meghatározott értékeket másképpen kérdezzük le. A jegyzet további részében ezen utóbbi módszer szerint fogunk eljárni.

olvasStr :: IO () olvasStr = do

putStr "kerek egy karakterlancot: "

str <- getLine

let x = filter isAlpha str let y = filter isNumber str

putStrLn $ "Az alfanumerikus karakterek: " ++ x putStrLn $ "A szamok: " ++ y

> olvasStr

kerek egy karakterlancot: Marosvasarhely 2001 oktober 1 Az alfanumerikus karakterek: Marosvasarhelyoktober A szamok: 20011

6.5. feladat Írjunk egy Haskell-függvényt, amely meghatározza a billentyű-zetről beolvasott számok rendezett sorrendjét. Használjuk asort könyvtár-függvényt.

import Data.List (sort) foRendez :: IO ()

foRendez = do

putStr "szamokat kerek: "

ls <- olvasSzamok1 let sortLs = sort ls putStr "rendezve: "

print sortLs

olvasSzamok1 :: IO [Int]

olvasSzamok1 = do temp <- getLine

let ls = read temp :: [Int]

return ls

> foRendez

szamokat kerek: [12, 4, 67, 8, 9]

rendezve: [4,8,9,12,67]

Hasonlóan az előző példához, egydoblokk keretén belül afoRendez függ-vényben több akció kerül végrehajtásra.

Az olvasSzamok1 függvény számok beolvasását biztosítja, amelyben a returnsegítségével azlslistát becsomagoljuk, azaz létrehozunk egyIO monádot, így azolvasSzamok1kimenetének típusaIO [Int]lesz. Itt vi-gyáznunk kell arra, hogy helyes formátumban olvassuk be a bemenetet, azaz ha nem megfelelő helyen használunk szögletes zárójeleket, illetve vesszőket, akkor futási hibát kapunk, ahogy a következő lekérdezésből ez kitűnik:

6.3. A standard bemenet és kimenet 133

> foRendez

szamokat kerek: 12, 4, 7, 8

rendezve: *** Exception: Prelude.read: no parse...

6.6. feladat Írjunk egy Haskell-függvényt, amely valós számokat olvas be a billentyűzetről, majd a számokat az egészrészük alapján különböző csopor-tokba, azaz listákba teszi.

import Data.List (sort, groupBy) foCsoportosit :: IO ()

foCsoportosit = do

putStr "szamokat kerek: "

ls <- olvasSzamok2

let gLs = groupBy (\x y -> truncate x == truncate y)

$ sort ls putStr "csoportositva: "

print gLs

olvasSzamok2 :: IO [Double]

olvasSzamok2 = do temp <- getLine

let ls = map (read :: String -> Double) $ words temp return ls

> foCsoportosit

szamokat kerek:1.77 5.6 1.4 5.34 2.7 2.9 1.9 2.4 1.33 csoportositva:

[[1.33,1.4,1.77,1.9],[2.4,2.7,2.9],[5.34,5.6]]

Ahogy a korábbi példáknál láttuk, afoCsoportositfüggvény itt is akciók egymásutánjából áll, amelyek adatbevitelt, illetve adatfeldolgozást tesznek lehetővé. Az előző példához képest a words és map függvények használa-ta miatt a számok bevitelét azonban más formában kell megadni. Most a számokat egy sorban, szóközöket téve közéjük kell beírni, mert ellenkező esetben futási hibát kapunk. Az olvasSzamok2 függvényben a temp-be beolvasott karakterláncot a words függvénnyel előbb szavakra bontjuk, azaz létrehozunk egy String elemekből álló listát, majd a map segítsé-gével egyenként átalakítjuk őketDoubletípusúvá, így a függvény kimenete IO [Double]lesz.

Az egészrész alapján történő csoportosításhoz először a sort függ-vénnyel rendezzük azlslistát, majd agroupByfüggvénnyel csoportosítjuk

az egymás után következő, azonos egész résszel rendelkező elemeket. Az egész részt a truncatekönyvtárfüggvénnyel határozzuk meg.

A csoportok kiíratását elegánsabb formában is megoldhatjuk, ha min-den sorba csak az azonos csoportbeli elemeket írjuk ki, nem mint listákat, hanem egyenként. Ennek megfelelően módosítjuk afoCsoportositutolsó sorát, és egy saját kiíró függvényt adunk meg, a myPrintList-et:

foCsoportosit_ :: IO () foCsoportosit_ = do

putStr "szamokat kerek: "

ls <- olvasSzamok2

let gLs = groupBy (\x y -> truncate x == truncate y)

$ sort ls putStr "csoportositva: \n"

myPrintList gLs

myPrintList :: [[Double]] -> IO () myPrintList = mapM_ auxF1

where

auxF1 :: [Double] -> IO () auxF1 kLs = do

mapM_ auxF2 kLs putStrLn ""

auxF2 :: Double -> IO ()

auxF2 k = putStr $ show k ++ " "

> foCsoportosit_

szamokat kerek: 1.77 5.6 1.4 5.34 2.7 2.9 1.9 2.4 1.33 csoportositva:

1.33 1.4 1.77 1.9 2.4 2.7 2.9

5.34 5.6

AmyPrintListfüggvényben kétszer is használatra került a már korábban is alkalmazottmapM_beépített függvény. Az elsőmapM_a sorok kiíratásáért felelős, a második a sorokon belüli értékeket írja ki, szóközt téve az elemek közé.

Haskellben nincs egész, illetve valós számokat beolvasó könyvtárfügg-vény, de ez könnyen megoldható. Korábban megadtuk az olvasInt függ-vényt, amelynek visszatérési értéke Int volt, a következő olvasDouble pedigDouble típusú érték beolvasását teszi lehetővé.

6.3. A standard bemenet és kimenet 135 olvasDouble :: IO Double

olvasDouble = do str <- getLine

return (read str :: Double)

A következő függvény több valós számot olvas be a billentyűzetről, ahol minden szám beolvasása után enter-t kell nyomni. Paraméterként meg kell adni, hogy hány számot szeretnénk beolvasni, a függvény pedig egy IO [Double] típusú értéket határoz meg. A kimeneti listában a számok sorrendje megegyezik a beolvasási sorrenddel.

olvasNszam :: Int -> IO [Double]

olvasNszam n = do k <- olvasDouble

if n == 1 then return [k]

else do

ve <- olvasNszam (n-1) return (k : ve)

> olvasNszam 3 12.6

34.3 5.7

[12.6,34.3,5.7]

A következő olvasSzam függvény egész számok beolvasását végzi, és addig kéri a számokat, amíg üres sort nem adunk. A függvény által megha-tározott kimenet típusaIO [Int]lesz, és megadja a beolvasott számokat, fordított sorrendben, mint ahogy beolvastuk őket a billentyűzetről.

olvasSzam :: IO [Int]

olvasSzam = auxOlvas []

where

auxOlvas :: [Int] -> IO [Int]

auxOlvas res = do temp <- getLine

if null temp then return res else do

let k = read temp :: Int auxOlvas (k : res)

> olvasSzam 3

6

7 [7,6,3]

A következő példák számrendszerek közötti átalakításokat fognak be-mutatni, amelyeket további I/O műveleteket végző példáknál fogunk fel-használni.

6.7. feladat Írjunk egy Haskell-függvényt, amely meghatározza egy szám tetszőleges számrendszerbeli alakja alapján a szám 10-es számrendszerbeli alakját.

convToNr :: (Integral a) => [a] -> a -> a convToNr ls b = auxConv ls b 0

where

auxConv :: (Num a) => [a] -> a -> a -> a auxConv [] b res = res

auxConv (k : ve) b res = auxConv ve b (k + b * res)

> convToNr [1,1,1,0,1] 2 29

A convtToNr függvény első paramétere egy lista, amely a b számrend-szerbeli számjegyeket fogja tartalmazni. Kimenete a10-es számrendszerbeli szám. A függvény egy auxConvsegédfüggvényt használ, ami lehetővé teszi a kezdeti értékadást, illetve hogy az eredményt a harmadik paraméteré-ben akkor számoljuk, amikor megyünk be a rekurzióba, ahol az eredmény meghatározása azt jelenti, hogy a megadott számjegyekből kiszámítjuk a megfelelő hatványok összegét. Első ránézésre lehet, hogy nem egyértelmű a hatványösszeg meghatározása, ezért a fenti bemenetre bemutatjuk a lépé-senkénti műveleteket:

k : ve res --> k + 2 * res

[1,1,1,0,1] 0

[1,1,0,1] -> 1 + 2·0

[1,0,1] -> 1 + 2·(1 + 2·0)

[0,1] -> 1 + 2·(1 + 2·(1 + 2·0))

[1] -> 0 + 2·(1 + 2·(1 + 2·(1 + 2·0)))

[] -> 1 + 2·(0 + 2·(1 + 2·(1 + 2·(1 + 2·0)))) -> 1 + 21·0 + 22·1 + 23· 1 + 24·1 + 25·0 -> 29

A fordított műveletet a convFromNr függvény végzi, amelynek beme-neti paraméterként meg kell adni egy tízes számrendszerbeli számot és egy

6.3. A standard bemenet és kimenet 137 bszámrendszert, hogy eredményként meghatározza egyIntelemtípusú lis-tába a bszámrendszerbeli számjegyeket.

6.8. feladat Írjunk egy Haskell-függvényt, amely meghatározza egy 10-es számrendszerbeli szám tetszőleges bszámrendszerbeli alakját.

convFromNr :: Integral a => a -> a -> [a]

convFromNr nr b = auxConv nr b []

where

auxConv :: Integral a => a -> a -> [a] -> [a]

auxConv nr b res

| nr < b = nr : res

| otherwise = auxConv d b (r : res) where

r = rem nr b d = div nr b

> convFromNr 256 2 [1,0,0,0,0,0,0,0,0]

6.9. feladat Olvassuk be a billentyűzetről egy szám számjegyeit és a meg-felelő számrendszer értékét. Alakítsuk át a számot 10-es számrendszerbe, majd ellenőrizzük az eredményt, használjuk a korábban megírt convToNr és convFromNrfüggvényeket.

foAlakit :: IO () foAlakit = do

putStr "szamjegyek: "

temp <- getLine

let ls = map (read :: String -> Integer) $ words temp putStr "szr alap: "

str <- getLine

let b = read str :: Integer let nr = convToNr ls b putStr "eredmeny: "

print nr

let ls = convFromNr nr b putStr "ellenorzes: "

print ls

> foAlakit

szamjegyek: 11 6 14 14 1 szr alap: 16

eredmeny: 749281

ellenorzes: [11,6,14,14,1]

Megjegyzés: az eredmény helyességét a következő összefüggés alapján tudjuk leellenőrizni: 749281 = 11·164 + 6·163 + 14·162 + 14·16 + 1.

Összegzésképpen elmondhatjuk, hogy a Haskell két szempont szerint figyeli a programkódot: mikor kell kifejezést, azaz tiszta függvényt kiérté-kelni, illetve mikor kell egy adott akciót, azaz mellékhatással járó műveletet végrehajtani. A kifejezések kiértékelésekor nem parancsvégrehajtás törté-nik, ezért ezekben a függvényekben nem léphetnek fel mellékhatások, ez a tulajdonképpeni tiszta funkcionális stílusban írt programrészek megadását jelenti. AzI/Oműveletek, azaz akciók végrehajtásakor előállhatnak mellék-hatások, ez az imperatív stílusú programrészek megadását jelenti.