기타 몇 가지 하스켈 초급 문제들을 해결해보고, 입출력을 처리하는 과정을 살펴보자.
리스트를 뒤집기
일련의 리스트를 입력된 순서를 거꾸로하여 출력한다. 단,
reverse
함수를 쓰지 말 것. (https://www.hackerrank.com/challenges/fp-reverse-a-list/problem)
이 문제는 reverse
함수를 구현해보라는 의미이다. 예를 들어 1 2 3 의 리스트가 있다고 할 때, 이를 (2 3을 뒤집은 결과) + [1] 로 만드는 처리를 재귀적으로 수행하면 역순의 리스트를 만들 수 있다. 따라서 다음과 같은 함수를 정의할 수 있다.
rev :: [a] -> [a]
rev [] = []
rev (x:xs) = (rev xs) ++ [x]
실제로 이 구현은 '매우 정직한' 뒤집기 함수로서 잘 동작한다. 그 외에도 여러 가지 방법이 있을 수 있다.
import Data.List ((!!))
rev2 lst = let l = length lst in [lst !! (l-x) | x <- [1..l]
하지만 그 중에 가장 좋은 것은 foldl
을 이용하는 방법이다.
foldl :: (Foldable t) => (b -> a -> b) -> b -> t a -> b
foldl
의 타입에서 알 수 있듯이 누적값과 새 원소를 이용해서 새 누적값을 만드는 함수와 초기값, 그리고 접기가 가능한 a
의 구조를 통해서 reduce
하는 함수인데, 앞에서부터 접어서 나간다.
만약 첫 누적값이 []
이고 리스트의 앞에서부터 이 누적값에 x:[]
와 같은 식으로 덧붙여나가면 4:3:2:1:[]
이런 식으로 뒤집은 리스트를 만들 수 있을 것이다.
따라서,
rev = foldl (\xs y -> y:xs) []
이렇게 정의할 수 있다.
최종코드
-- h005.hs
import Control.Monad
rev = foldl (\xs y -> y:xs) []
main = getContents >>= mapM_ print . rev . words
홀수값의 합
주어진 리스트를 정수의 리스트로 읽어내고, 그 중 홀수만 추려서 합을 구한다. 이는 odd
함수로 필터링해서 sum
하면 끝
-- h006.hs
f = sum . filter odd
main = getContents >>= print . f . (map read) . words
리스트 갱신
리스트는 '갱신가능한' 타입이 아니다. 대신에 함수에 넣어서 다른 리스트로 바꿔야 한다. 이 문제에서는 입력받은 일련의 정수리스트의 절대값을 출력한다.
f = map abs
main = do
inputData <- getContents
mapM_ putStrLn . map show . f . map (read :: String -> Int) . lines $ inputData
최대공약수 구하기
유클리드의 호제법을 이용한 최대공약수를 구하는 문제이다. 다른 언어에서도 재귀를 이용해서 구하므로 어렵지 않다.
gcd' :: Integral a => a -> a -> a -> a
gcd' n m =
| m == 0 = n
| otherwise = gcd' m (n `mod` m)
main :: IO ()
main = getContents >>=
putStrLn . show . (uncurry gcd') . list2tuple . map read . words
where list2tuple (x:y:_) = (x, y)
참고로 하스켈의 기본 라이브러리인 Prelude에는 gcd
, lcm
함수를 이미 구비하고 있다. 따라서 '
을 붙여서 별개의 함수로 이름 지었다.
입출력 흐름을 잘 살펴보자.
- 입력된 라인을 공백단위로 쪼개고 (
words
) - 쪼개진 단어들을 정수로 변환하도록 맵핑한다. (
map read
) - 이제 정수 2개짜리 리스트를 튜플로 변환하고 (
list2tuple
) - 이 값을 튜플을 받도록 변경된 (
uncurry gcd'
)gcd'
함수에 전달한다. - 그 결과를 문자열로 변환해서
show
- 화면에 출력한다.
putStrLn
이 흐름은 기본적으로 문자열을 받아서 IO 액션으로 끝났다. 따라서 String -> IO ()
의 모양이 된다.
>>=
은 모나드에 대해서 Monad m => m a -> (a -> m b) -> m b
타입의 연산으로 String -> IO ()
에 IO String
값을 주입하는 역할을 한다.
이제 해당 처리 과정이 눈에 들어오는지?