Graphics Programming

[ALGOSPOT] ENDIANS - IO 액션 직접 만들어보기 본문

Season 1/하스켈

[ALGOSPOT] ENDIANS - IO 액션 직접 만들어보기

minseoklee 2014. 10. 4. 09:26
문제: https://algospot.com/judge/problem/read/ENDIANS

1. 정수 N을 입력받는다.
2. N개의 32비트 정수를 입력받아 반대 엔디안으로 바꿔서 출력한다.

워낙 간단한 문제니 전략을 바로 세울 수 있다. 문제 하나를 푸는 액션 solve를 정의한다. N을 입력받아서 solve를 N번 실행한다. 하스켈 코드로 그 개요를 표현하자면 다음과 같다.

import Control.Monad

-- replicateM_은 여러 액션을 실행하고 결과는 버린다 (액션들을 >> 연산자로 연쇄하는 것과 같은 효과)
main = 입력 >>= (\ 문제수 -> replicateM_ 문제수 solve)


문제는 입력이 4 123 3166 4232 874 처럼 띄어쓰기 형태로 들어올 경우 어떻게 하냐는 것인데.. 실제로는 줄바꿈(line break)로 나눠져서 들어온다 쳐도 궁금하니까 띄어쓰기를 가정하고 풀어보기로 했다. 그럼 입력 함수를 찾아볼까...

했는데 getLine :: IO String과 getChar :: IO Char 밖에 못 찾았다. 다행히 입력이 무조건 32비트 양의 정수이므로 getChar를 사용하면 구현할 수 있을 것 같다. 입력이 '0'~'9' 중 하나면 계속 덧붙이고 아니면 끝내는 것이다. getInt :: IO Int 같은 게 라이브러리에 있든 없든 구현해보면 공부는 되겠지 --

getInt :: IO Int
getInt = getChar >>= (\ c -> if ('0' <= c && c <= '9') then ??? else ???)


then에 뭘 넣어야 하지? getInt를 다시 호출하면 되나?

-- Data.Char 모듈의 digitToInt 함수는 '0' ~ '9' 사이의 문자를 대응하는 숫자로 변환한다
getInt :: IO Int
getInt n = getChar >>= (\ c -> if ('0' <= c && c <= '9') then (getInt n*10 + digitToInt c) else ???


아니다. 뭔가 이상하다. 우리는 getLine이나 getChar를 호출할 때 인자를 주지 않는다. 이렇게 하면 getInt를 실행할 때 getInt 0이라고 써야 한다.

getInt :: IO Int
getInt = sub 0
    where getChar >>= (\ c -> if ('0' <= c && c <= '9') then sub (n*10 + digitToInt c) else ???)


이제 재귀 부분은 끝났고 else에서는 실제로 IO Int로 평가되며 이 액션이 끝나야 한다. IO n이라고 쓰면 되나..? 아니다. Monad 클래스의 선언을 생각해보니 return 함수가 있었다. return의 타입은 return :: a -> m a다.

getInt :: IO Int
getInt = sub 0
    where getChar >>= (\ c -> if ('0' <= c && c <= '9') then sub (n*10 + digitToInt c) else return n)


그런데 이게 정말 작동은 할까? 함수를 정의하는 거야 당연히 잘 될 것 같은데 액션을 정의하려니 조금 생소하다. 모르겠다 그냥 타입은 맞으니까 된다고 생각해야지 --

-- getInt 실험
main = getInt >>= (\ num -> print num)


되..된다!

이제 getInt를 이용해서 맨 처음 코드를 진행할 수 있다.

import Control.Monad

main = getInt >>= (\ numProbs -> replicateM_ numProbs solve)

solve = getInt >>= (\ num -> print (convert num))

getInt :: IO Int
getInt = sub 0
    where
sub n = getChar >>= (\ c ->
if ('0' <= c && c <= '9')
then sub (n*10 + digitToInt c)
else return n)

-- 엔디안 변환하는 함수
convert :: Int -> Int
convert = ???


이제 convert 함수에서 비트 조작을 해야 하는데 찾아보니 Data.Bits 모듈에 타 언어에도 으레 있는 비트 조작 함수들이 있다. 특수 기호를 다른 종류의 연산자들에 너무 많이 써서 그런지 비트 조작 연산자들은 죄다 이상한 기호거나 아예 일반 함수를 중위로 써야 한다. (논리곱은 .&. 이고 우측 쉬프트는 `shiftR` 등) 어쨌든 비트 조작은 중요한 게 아니니까 이리저리 끼워맞추고 최종 코드를 정리하면

import Data.Bits
import Data.Char
import Control.Monad

getInt :: IO Int
getInt = sub 0
    where
sub n = getChar >>= (\ c ->
if ('0' <= c && c <= '9')
then sub (n*10 + digitToInt c)
else return n)

convert :: Int -> Int
convert n = (b1 `shiftL` 24) .|. (b2 `shiftL` 16) .|. (b3 `shiftL` 8) .|. b4
where
b1 = n .&. 0xFF
b2 = (n `shiftR` 8) .&. 0xFF
b3 = (n `shiftR` 16) .&. 0xFF
b4 = (n `shiftR` 24)

solve = getInt >>= (\ n -> print (convert n))

main = getInt >>= (\ numProbs -> replicateM_ numProbs solve)


Comments