SlideShare a Scribd company logo
1 of 31
Download to read offline
Haskell
Study
10. Baseball Game
Baseball game
이제 Haskell로 간단한 프로그램을 만들 수 있는 준비는 모두 마쳤습니다. 연습삼아 Baseball game
을 콘솔로 만들어봅시다.
Baseball Game 규칙은 다음과 같습니다.
- 정답은 서로 겹치는 자릿수가 없는 세자리의 숫자입니다(123, 549, 608 등).
- 플레이어는 정답이 뭔지 추측해야합니다.
- 정답과 플레이어가 낸 답을 비교해서, 각 자릿수에 대해 숫자와 위치가 모두 일치할 경우 Strike,
숫자만 일치할 경우 Ball이 됩니다. (ex - 정답이 123일 때 124는 2S 0B, 231은 0S 3B)
- 정답을 맞추면 게임이 끝납니다.
Start
우선 적당한 파일을 만들고(여기서는 baseball.hs) main 함수부터 작성해봅시다.
import Data.List
import System.Random
import Control.Monad
main = do
	print "hello!"
나중에 쓸 필요가 있는 함수들을 우선 import 해봅니다. main함수를 적당히 작성하고 컴파일되는
지부터 확인해봅시다.
Make Answer
Baseball game을 시작하려면 우선 정답을 생성해야합니다. 게임을 할 때마다 항상 정답이 같으면
게임을 하는 의미가 없으니 정답은 랜덤으로 생성해야겠죠. 정답을 생성하는 함수 makeAnswer를
만들어봅시다. makeAnswer 함수의 타입 서명은 다음과 같습니다.
makeAnswer :: StdGen -> Int
StdGen은 System.Random에 있는 타입으로, 랜덤 값을 생성하기 위한 시드입니다. getStdGen
함수를 통해 얻어올 수 있습니다(getStdGen :: IO StdGen). main 함수에서 StdGen을 생성해
봅시다.
main = do
	gen <- getStdGen
	 let answer = makeAnswer gen
	 print answer -- print 함수는 putStrLn . show와 같습니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
randomRs 함수는 숫자 범위와 StdGen
값을 받아서 해당 범위 내의 있는
랜덤 값들로 이루어진 무한 리스트를
반환합니다. 이 무한 리스트로부터 정답이
될 수 있는 후보군(정답이 가져야할 조건을
만족하는 값들)을 만들고 그 후보군에서 맨
첫 번째 값을 정답으로 삼습니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
candidates가 바로 후보군입니다.
여기에는 후보가 될 수 있는 모든 값들이
포함되지만, Haskell의 laziness 특성에
의해 모든 후보군을 구하지 않고 제일
첫 번째 후보군만 구하게 됩니다(head
candidates). 여기서 정답이 될 후보군을
구하는 과정은 비결정적 연산(각 자릿수에
들어갈 수 있는 모든 경우의 수를 다
따진다. 어떤 것이 정답이 될 지 모른다.)
이므로 list monad를 사용합니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
h는 100의 자리에 해당하는 자릿수 입니다
무한 리스트인 rands로부터 하나씩 값을
꺼내서 해당 값이 조건을 만족하는지
테스트합니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
guard 함수는 Control.Monad에 있는
함수로, list comprehension에서 술어가
사실 guard를 이용해 구현됩니다. 우선은
조건이 만족되지 않으면 다음 연산으로
넘어가지 못하게 막는, 조건을 만족해야만
다음 연산을 수행하게 만드는 거라고
생각합시다. 정확한 구현이 궁금하다면
Monoid 및 guard의 구현 코드를
살펴보시는 걸 추천드립니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
h(100의 자리)값은 구했으니, 다음은 10
의 자리 값 t를 구해봅시다. 모든 자릿수는
겹치면 안되기 때문에 guard (h /= t)
조건이 붙었습니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
1의 자리(o) 역시 마찬가지 입니다. 10의
자리, 100의 자리 값과 겹치면 안되므로
이를 guard를 통해 구현했습니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
모든 조건을 만족하는 h, t, o 값에 대해
이를 세자리의 숫자로 만듭니다. 이 결과로
candidates에는 조건을 만족하는 모든
정답 후보 숫자가 들어가게 됩니다.
Make Answer
이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다.
makeAnswer :: StdGen -> Int
makeAnswer gen = head candidates
where rands = randomRs (0, 9) gen
candidates = do
h <- rands
guard (h /= 0)
t <- rands
guard (h /= t)
o <- rands
guard (o /= t && o /= h)
return (h*100 + t*10 + o)
그리고 정답은 그 후보군에 속하는 숫자
중 제일 첫 번째 숫자입니다. laziness
에 의해 무한 리스트를 모두 계산하지
않고 후보군이 하나라도 나오면 나머지는
계산하지 않고 바로 그 답을 리턴합니다.
reads
이제 입력받는 함수를 구현할 차례입니다. 우선 reads 함수에 대해 짚고 넘어갑시다. reads 함수는
read함수가 parsing에 실패할 경우 예외를 발생하는 것과 달리 예외를 발생시키지 않습니다. 좀
더 안전한 함수라고 볼 수 있습니다. reads 함수는 String -> [(a, String)] 이라는 타입을 갖고
있습니다. 주어진 String으로부터 읽고자 하는 타입이 a, parsing되지 못한 나머지 부분이 튜플의 두
번째 원소 String으로 들어갑니다. parsing에 실패할 경우 []를 리턴합니다.
Prelude> reads "123abc" :: [(Int, String)]
[(123, "abc")]
Prelude> reads "abc123" :: [(Int, String)]
[]
Prelude> reads " 12" :: [(Int, String)]
[(12, "")]
getInput
사용자로부터 입력받은 값(문자열)로부터 baseball game의 input을 얻어내는 함수를
만들어봅시다. 잘못된 입력이 있을 수 있으므로 성공할 경우 Just input을, 그렇지 않을 경우
Nothing을 리턴하도록 만듭시다.
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
우선 reads 함수를 이용해
raw 값을 읽은 다음,
go 함수를 이용해 해당
입력이 조건을 만족하는지
테스트합니다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
텅 빈 리스트가 나왔다는
건 parsing에 실패한
경우이므로 잘못된
입력입니다. Nothing을
리턴합시다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
리스트 내에 원소가 하나고,
튜플에서 두번째 원소가 텅
빈 리스트면 일단 입력으로
숫자 하나가 들어왔다는
뜻입니다. 이제 이 숫자가
정당한 입력 조건을
만족하는 지 봅시다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
저는 여기서 Input이 0이
들어올 경우 프로그램이
종료되게 만들려고
했기 때문에 값이 0인
경우도 정당한 입력으로
취급했습니다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
그 외의 경우엔 값이
세자리수가 아니라면
잘못된 것이겠죠. Nothing
을 리턴합니다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
숫자가 세 자리수고, 하나도
겹치는게 없다면 그 값은
정당한 입력입니다. Just i
를 리턴합니다.
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
그 외의 경우는 무조건
Nothing을 리턴하는게
맞겠죠(중복되는 자릿수가
있는 경우).
getInput
getInput :: String -> Maybe Int
getInput raw = go (reads raw)
where go [] = Nothing
go [(i, [])]
| i == 0 = Just 0
| i > 999 || i < 100 = Nothing
| (length . nub . show) i == 3 = Just i
| otherwise = Nothing
go _ = Nothing
패턴 매칭에서 나머지
경우에는 역시 정당한
입력이 아닐 것이므로
Nothing을 리턴합니다.
play
다음은 play 함수입니다. 이 함수는 실제 게임 플레이 부분을 담당합니다.
main = do
gen <- getStdGen
play $ makeAnswer gen --정답을 생성한 후 정답 값을 이용해 게임 플레이를 합니다.
play :: Int -> IO ()
play answer = do
putStrLn "guess the answer!(0 : exit)"
rawInput <- getLine
let input = getInput rawInput
case input of Just 0 -> end
Nothing -> retry answer
Just guess -> check answer guess
play
let input = getInput rawInput
case input of Just 0 -> end
Nothing -> retry answer
Just guess -> check answer guess
핵심은 위 코드입니다. getInput 함수를 통해 input값을 가져온 후, input 값이 Just 0이라면
종료이므로 end 함수를 호출, Nothing이라면 잘못된 입력이므로 다시 입력을 시도(retry), 제대로
된 입력이 들어왔다면 정답이 맞는지 아닌지 확인해야하므로 check 함수를 호출합니다.
이제 이 3개의 함수만 구현하면 됩니다.
end
end :: IO ()
end = do
putStrLn "game over."
end 함수는 간단합니다. 그냥 프로그램 종료에 해당하는 문구를 출력한 후 프로그램을 종료시킵니다.
만약 다른 어떤 작업을 하고 싶다면 이 함수에 어떤 동작을 추가하면 되겠죠.
retry
retry :: Int -> IO ()
retry answer = do
putStrLn "invalid input."
play answer
retry 함수는 단순히 잘못된 입력이 들어왔음을 화면에 출력한 후, play 함수를 다시 호출합니다.
play 함수를 다시 호출해서 입력부터 다시 시작하는 거죠.
check
check :: Int -> Int -> IO ()
check answer guess
| answer == guess = do
putStrLn "you are right!"
end
| otherwise = do
let (s, b) = getStrikeAndBall answer guess
putStrLn $ show s ++ " strike, " ++ show b ++ " ball"
play answer
check 함수는 정답을 맞췄을 경우, 아닐 경우 두 가지가 있습니다. 정답을 맞췄을 경우 정답임을
출력하고 종료, 그렇지 않을 경우 strike, ball 개수를 출력한 후 play 함수를 호출함으로써 다시
정답을 추론하게 만듭니다.
getStrikeAndBall
getStrikeAndBall :: Int -> Int -> (Int, Int)
getStrikeAndBall answer guess = (strike, ball)
where a = show answer
g = show guess
strike = length $ filter ((x,y) -> x == y) (zip a g)
ball = (length $ filter (x -> x `elem` a) g) - strike
getStrikeAndBall 함수는 추측 값과 정답이 주어졌을 때 strike, ball 개수를 튜플 형태로
반환합니다. 로직은 단순한 편이니 쉽게 이해할 수 있을거에요. strike는 answer와 guess를 zip한
후 튜플의 두 요소가 일치하는 개수를, ball은 guess의 자릿수중 answer에 포함되는 이들의 개수를
구한 후 거기서 strike 개수만큼 제외합니다.
result
작성한 코드를 컴파일해서 실행해보면 다음과 같은 결과를 얻을 수 있습니다.
guess the answer!(0 : exit)
123
0 strike, 1 ball
guess the answer!(0 : exit)
111
invalid input.
guess the answer!(0 : exit)
456
1 strike, 0 ball
guess the answer!(0 : exit)
789
0 strike, 1 ball
guess the answer!(0 : exit)
159
1 strike, 0 ball
guess the answer!(0 : exit)
258
1 strike, 0 ball
guess the answer!(0 : exit)
357
you are right!
game over.
more..
baseball 게임에 다음 요소들을 추가해봅시다.
•	 턴 수 출력
정답을 맞추는데 까지 몇 턴이 걸렸는지를 출력해봅시다.
•	 다시 시작
정답을 맞추면 프로그램을 종료하지 말고, 새로운 정답을 기반으로 다시 플레이를 하게 만들어봅시다.

More Related Content

What's hot

Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730
Yong Joon Moon
 
파이썬 문자열 이해하기
파이썬 문자열 이해하기파이썬 문자열 이해하기
파이썬 문자열 이해하기
Yong Joon Moon
 
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
해강
 

What's hot (20)

Haskell study 6
Haskell study 6Haskell study 6
Haskell study 6
 
Haskell study 7
Haskell study 7Haskell study 7
Haskell study 7
 
Haskell study 2
Haskell study 2Haskell study 2
Haskell study 2
 
파이썬 Numpy 선형대수 이해하기
파이썬 Numpy 선형대수 이해하기파이썬 Numpy 선형대수 이해하기
파이썬 Numpy 선형대수 이해하기
 
Haskell study 3
Haskell study 3Haskell study 3
Haskell study 3
 
Python Sympy 모듈 이해하기
Python Sympy 모듈 이해하기Python Sympy 모듈 이해하기
Python Sympy 모듈 이해하기
 
[Swift] Functions
[Swift] Functions[Swift] Functions
[Swift] Functions
 
Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730Matplotlib 기초 이해하기_20160730
Matplotlib 기초 이해하기_20160730
 
파이썬+Operator+이해하기 20160409
파이썬+Operator+이해하기 20160409파이썬+Operator+이해하기 20160409
파이썬+Operator+이해하기 20160409
 
Start IoT with JavaScript - 5.객체2
Start IoT with JavaScript - 5.객체2Start IoT with JavaScript - 5.객체2
Start IoT with JavaScript - 5.객체2
 
파이썬 문자열 이해하기
파이썬 문자열 이해하기파이썬 문자열 이해하기
파이썬 문자열 이해하기
 
Processing 기초 이해하기_20160713
Processing 기초 이해하기_20160713Processing 기초 이해하기_20160713
Processing 기초 이해하기_20160713
 
Lua 문법
Lua 문법Lua 문법
Lua 문법
 
Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815
 
Lua 문법 -함수
Lua 문법 -함수Lua 문법 -함수
Lua 문법 -함수
 
Startup JavaScript 4 - 객체
Startup JavaScript 4 - 객체Startup JavaScript 4 - 객체
Startup JavaScript 4 - 객체
 
Start IoT with JavaScript - 4.객체1
Start IoT with JavaScript - 4.객체1Start IoT with JavaScript - 4.객체1
Start IoT with JavaScript - 4.객체1
 
하스켈 프로그래밍 입문
하스켈 프로그래밍 입문하스켈 프로그래밍 입문
하스켈 프로그래밍 입문
 
[devil's camp] - 알고리즘 대회와 STL (박인서)
[devil's camp] - 알고리즘 대회와 STL (박인서)[devil's camp] - 알고리즘 대회와 STL (박인서)
[devil's camp] - 알고리즘 대회와 STL (박인서)
 
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
[아꿈사] The C++ Programming Language 11장 연산자 오버로딩
 

Viewers also liked

Viewers also liked (18)

Herramientas tic para la capacitacion
Herramientas tic para la capacitacionHerramientas tic para la capacitacion
Herramientas tic para la capacitacion
 
Trastornos Metabolicos (Acidosis, Hiperpotasemia, Trastornos Minerales y Oseo...
Trastornos Metabolicos (Acidosis, Hiperpotasemia, Trastornos Minerales y Oseo...Trastornos Metabolicos (Acidosis, Hiperpotasemia, Trastornos Minerales y Oseo...
Trastornos Metabolicos (Acidosis, Hiperpotasemia, Trastornos Minerales y Oseo...
 
Problems of Borderline
Problems of BorderlineProblems of Borderline
Problems of Borderline
 
Asn services technical data sheet road blocker
Asn services technical data sheet road blockerAsn services technical data sheet road blocker
Asn services technical data sheet road blocker
 
REVISTA: proteinuria 24 h vs albumina / creatinina azar
REVISTA: proteinuria 24 h vs albumina  / creatinina azarREVISTA: proteinuria 24 h vs albumina  / creatinina azar
REVISTA: proteinuria 24 h vs albumina / creatinina azar
 
Balance he
Balance heBalance he
Balance he
 
Gpg 1.1
Gpg 1.1Gpg 1.1
Gpg 1.1
 
Employment laws
Employment lawsEmployment laws
Employment laws
 
Memory & object pooling
Memory & object poolingMemory & object pooling
Memory & object pooling
 
Stl vector, list, map
Stl vector, list, mapStl vector, list, map
Stl vector, list, map
 
Gestiónycalidadeduc.bás.casos
Gestiónycalidadeduc.bás.casosGestiónycalidadeduc.bás.casos
Gestiónycalidadeduc.bás.casos
 
Database
DatabaseDatabase
Database
 
구문과 의미론(정적 의미론까지)
구문과 의미론(정적 의미론까지)구문과 의미론(정적 의미론까지)
구문과 의미론(정적 의미론까지)
 
Iocp advanced
Iocp advancedIocp advanced
Iocp advanced
 
Haskell study 11
Haskell study 11Haskell study 11
Haskell study 11
 
Multi thread
Multi threadMulti thread
Multi thread
 
Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약Effective c++ chapter 1,2 요약
Effective c++ chapter 1,2 요약
 
NEFROLOGIA CLINICA: Nefrolitiasis
NEFROLOGIA CLINICA: NefrolitiasisNEFROLOGIA CLINICA: Nefrolitiasis
NEFROLOGIA CLINICA: Nefrolitiasis
 

Similar to Haskell study 10

2012 Dm C3 03
2012 Dm C3 032012 Dm C3 03
2012 Dm C3 03
chl132435
 
이산치보고서
이산치보고서이산치보고서
이산치보고서
mil23
 
Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기
지수 윤
 
11. array & pointer
11. array & pointer11. array & pointer
11. array & pointer
웅식 전
 
자료구조 Project6
자료구조 Project6자료구조 Project6
자료구조 Project6
KoChungWook
 
2012 Ds B1 01
2012 Ds B1 012012 Ds B1 01
2012 Ds B1 01
seonhyung
 
C수업자료
C수업자료C수업자료
C수업자료
koominsu
 

Similar to Haskell study 10 (20)

프로젝트 보고서
프로젝트 보고서프로젝트 보고서
프로젝트 보고서
 
[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이
[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이
[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이
 
Seed2016 - 개미수열 한주영 (annotated)
Seed2016 - 개미수열 한주영 (annotated)Seed2016 - 개미수열 한주영 (annotated)
Seed2016 - 개미수열 한주영 (annotated)
 
파이썬 기본 문법
파이썬 기본 문법파이썬 기본 문법
파이썬 기본 문법
 
Example
ExampleExample
Example
 
사칙연산 프로그램
사칙연산 프로그램사칙연산 프로그램
사칙연산 프로그램
 
포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++포트폴리오에서 사용한 모던 C++
포트폴리오에서 사용한 모던 C++
 
2012 Dm C3 03
2012 Dm C3 032012 Dm C3 03
2012 Dm C3 03
 
이산치보고서
이산치보고서이산치보고서
이산치보고서
 
RPG Maker와 Ruby로 코딩 시작하기 Day 3
RPG Maker와 Ruby로 코딩 시작하기 Day 3RPG Maker와 Ruby로 코딩 시작하기 Day 3
RPG Maker와 Ruby로 코딩 시작하기 Day 3
 
Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기
 
11. array & pointer
11. array & pointer11. array & pointer
11. array & pointer
 
고등학생 R&E Python summary for test
고등학생 R&E Python summary for test고등학생 R&E Python summary for test
고등학생 R&E Python summary for test
 
자료구조 Project6
자료구조 Project6자료구조 Project6
자료구조 Project6
 
2012 Ds B1 01
2012 Ds B1 012012 Ds B1 01
2012 Ds B1 01
 
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
 
C수업자료
C수업자료C수업자료
C수업자료
 
C수업자료
C수업자료C수업자료
C수업자료
 
이산치1번
이산치1번이산치1번
이산치1번
 
파이썬 스터디 2주차
파이썬 스터디 2주차파이썬 스터디 2주차
파이썬 스터디 2주차
 

More from Nam Hyeonuk (7)

Next 게임 실전 프로젝트 슬라이드
Next 게임 실전 프로젝트 슬라이드Next 게임 실전 프로젝트 슬라이드
Next 게임 실전 프로젝트 슬라이드
 
Haskell study 1
Haskell study 1Haskell study 1
Haskell study 1
 
Haskell study 0
Haskell study 0Haskell study 0
Haskell study 0
 
Exception&log
Exception&logException&log
Exception&log
 
Iocp 기본 구조 이해
Iocp 기본 구조 이해Iocp 기본 구조 이해
Iocp 기본 구조 이해
 
Tcp ip & io model
Tcp ip & io modelTcp ip & io model
Tcp ip & io model
 
Age Of Empires II : Age Of Kings Postmotem
Age Of Empires II : Age Of Kings PostmotemAge Of Empires II : Age Of Kings Postmotem
Age Of Empires II : Age Of Kings Postmotem
 

Haskell study 10

  • 2. Baseball game 이제 Haskell로 간단한 프로그램을 만들 수 있는 준비는 모두 마쳤습니다. 연습삼아 Baseball game 을 콘솔로 만들어봅시다. Baseball Game 규칙은 다음과 같습니다. - 정답은 서로 겹치는 자릿수가 없는 세자리의 숫자입니다(123, 549, 608 등). - 플레이어는 정답이 뭔지 추측해야합니다. - 정답과 플레이어가 낸 답을 비교해서, 각 자릿수에 대해 숫자와 위치가 모두 일치할 경우 Strike, 숫자만 일치할 경우 Ball이 됩니다. (ex - 정답이 123일 때 124는 2S 0B, 231은 0S 3B) - 정답을 맞추면 게임이 끝납니다.
  • 3. Start 우선 적당한 파일을 만들고(여기서는 baseball.hs) main 함수부터 작성해봅시다. import Data.List import System.Random import Control.Monad main = do print "hello!" 나중에 쓸 필요가 있는 함수들을 우선 import 해봅니다. main함수를 적당히 작성하고 컴파일되는 지부터 확인해봅시다.
  • 4. Make Answer Baseball game을 시작하려면 우선 정답을 생성해야합니다. 게임을 할 때마다 항상 정답이 같으면 게임을 하는 의미가 없으니 정답은 랜덤으로 생성해야겠죠. 정답을 생성하는 함수 makeAnswer를 만들어봅시다. makeAnswer 함수의 타입 서명은 다음과 같습니다. makeAnswer :: StdGen -> Int StdGen은 System.Random에 있는 타입으로, 랜덤 값을 생성하기 위한 시드입니다. getStdGen 함수를 통해 얻어올 수 있습니다(getStdGen :: IO StdGen). main 함수에서 StdGen을 생성해 봅시다. main = do gen <- getStdGen let answer = makeAnswer gen print answer -- print 함수는 putStrLn . show와 같습니다.
  • 5. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o)
  • 6. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) randomRs 함수는 숫자 범위와 StdGen 값을 받아서 해당 범위 내의 있는 랜덤 값들로 이루어진 무한 리스트를 반환합니다. 이 무한 리스트로부터 정답이 될 수 있는 후보군(정답이 가져야할 조건을 만족하는 값들)을 만들고 그 후보군에서 맨 첫 번째 값을 정답으로 삼습니다.
  • 7. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) candidates가 바로 후보군입니다. 여기에는 후보가 될 수 있는 모든 값들이 포함되지만, Haskell의 laziness 특성에 의해 모든 후보군을 구하지 않고 제일 첫 번째 후보군만 구하게 됩니다(head candidates). 여기서 정답이 될 후보군을 구하는 과정은 비결정적 연산(각 자릿수에 들어갈 수 있는 모든 경우의 수를 다 따진다. 어떤 것이 정답이 될 지 모른다.) 이므로 list monad를 사용합니다.
  • 8. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) h는 100의 자리에 해당하는 자릿수 입니다 무한 리스트인 rands로부터 하나씩 값을 꺼내서 해당 값이 조건을 만족하는지 테스트합니다.
  • 9. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) guard 함수는 Control.Monad에 있는 함수로, list comprehension에서 술어가 사실 guard를 이용해 구현됩니다. 우선은 조건이 만족되지 않으면 다음 연산으로 넘어가지 못하게 막는, 조건을 만족해야만 다음 연산을 수행하게 만드는 거라고 생각합시다. 정확한 구현이 궁금하다면 Monoid 및 guard의 구현 코드를 살펴보시는 걸 추천드립니다.
  • 10. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) h(100의 자리)값은 구했으니, 다음은 10 의 자리 값 t를 구해봅시다. 모든 자릿수는 겹치면 안되기 때문에 guard (h /= t) 조건이 붙었습니다.
  • 11. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) 1의 자리(o) 역시 마찬가지 입니다. 10의 자리, 100의 자리 값과 겹치면 안되므로 이를 guard를 통해 구현했습니다.
  • 12. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) 모든 조건을 만족하는 h, t, o 값에 대해 이를 세자리의 숫자로 만듭니다. 이 결과로 candidates에는 조건을 만족하는 모든 정답 후보 숫자가 들어가게 됩니다.
  • 13. Make Answer 이제 makeAnswer 함수의 구현입니다. 코드부터 본 다음 한줄씩 이해해봅시다. makeAnswer :: StdGen -> Int makeAnswer gen = head candidates where rands = randomRs (0, 9) gen candidates = do h <- rands guard (h /= 0) t <- rands guard (h /= t) o <- rands guard (o /= t && o /= h) return (h*100 + t*10 + o) 그리고 정답은 그 후보군에 속하는 숫자 중 제일 첫 번째 숫자입니다. laziness 에 의해 무한 리스트를 모두 계산하지 않고 후보군이 하나라도 나오면 나머지는 계산하지 않고 바로 그 답을 리턴합니다.
  • 14. reads 이제 입력받는 함수를 구현할 차례입니다. 우선 reads 함수에 대해 짚고 넘어갑시다. reads 함수는 read함수가 parsing에 실패할 경우 예외를 발생하는 것과 달리 예외를 발생시키지 않습니다. 좀 더 안전한 함수라고 볼 수 있습니다. reads 함수는 String -> [(a, String)] 이라는 타입을 갖고 있습니다. 주어진 String으로부터 읽고자 하는 타입이 a, parsing되지 못한 나머지 부분이 튜플의 두 번째 원소 String으로 들어갑니다. parsing에 실패할 경우 []를 리턴합니다. Prelude> reads "123abc" :: [(Int, String)] [(123, "abc")] Prelude> reads "abc123" :: [(Int, String)] [] Prelude> reads " 12" :: [(Int, String)] [(12, "")]
  • 15. getInput 사용자로부터 입력받은 값(문자열)로부터 baseball game의 input을 얻어내는 함수를 만들어봅시다. 잘못된 입력이 있을 수 있으므로 성공할 경우 Just input을, 그렇지 않을 경우 Nothing을 리턴하도록 만듭시다. getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing
  • 16. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 우선 reads 함수를 이용해 raw 값을 읽은 다음, go 함수를 이용해 해당 입력이 조건을 만족하는지 테스트합니다.
  • 17. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 텅 빈 리스트가 나왔다는 건 parsing에 실패한 경우이므로 잘못된 입력입니다. Nothing을 리턴합시다.
  • 18. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 리스트 내에 원소가 하나고, 튜플에서 두번째 원소가 텅 빈 리스트면 일단 입력으로 숫자 하나가 들어왔다는 뜻입니다. 이제 이 숫자가 정당한 입력 조건을 만족하는 지 봅시다.
  • 19. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 저는 여기서 Input이 0이 들어올 경우 프로그램이 종료되게 만들려고 했기 때문에 값이 0인 경우도 정당한 입력으로 취급했습니다.
  • 20. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 그 외의 경우엔 값이 세자리수가 아니라면 잘못된 것이겠죠. Nothing 을 리턴합니다.
  • 21. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 숫자가 세 자리수고, 하나도 겹치는게 없다면 그 값은 정당한 입력입니다. Just i 를 리턴합니다.
  • 22. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 그 외의 경우는 무조건 Nothing을 리턴하는게 맞겠죠(중복되는 자릿수가 있는 경우).
  • 23. getInput getInput :: String -> Maybe Int getInput raw = go (reads raw) where go [] = Nothing go [(i, [])] | i == 0 = Just 0 | i > 999 || i < 100 = Nothing | (length . nub . show) i == 3 = Just i | otherwise = Nothing go _ = Nothing 패턴 매칭에서 나머지 경우에는 역시 정당한 입력이 아닐 것이므로 Nothing을 리턴합니다.
  • 24. play 다음은 play 함수입니다. 이 함수는 실제 게임 플레이 부분을 담당합니다. main = do gen <- getStdGen play $ makeAnswer gen --정답을 생성한 후 정답 값을 이용해 게임 플레이를 합니다. play :: Int -> IO () play answer = do putStrLn "guess the answer!(0 : exit)" rawInput <- getLine let input = getInput rawInput case input of Just 0 -> end Nothing -> retry answer Just guess -> check answer guess
  • 25. play let input = getInput rawInput case input of Just 0 -> end Nothing -> retry answer Just guess -> check answer guess 핵심은 위 코드입니다. getInput 함수를 통해 input값을 가져온 후, input 값이 Just 0이라면 종료이므로 end 함수를 호출, Nothing이라면 잘못된 입력이므로 다시 입력을 시도(retry), 제대로 된 입력이 들어왔다면 정답이 맞는지 아닌지 확인해야하므로 check 함수를 호출합니다. 이제 이 3개의 함수만 구현하면 됩니다.
  • 26. end end :: IO () end = do putStrLn "game over." end 함수는 간단합니다. 그냥 프로그램 종료에 해당하는 문구를 출력한 후 프로그램을 종료시킵니다. 만약 다른 어떤 작업을 하고 싶다면 이 함수에 어떤 동작을 추가하면 되겠죠.
  • 27. retry retry :: Int -> IO () retry answer = do putStrLn "invalid input." play answer retry 함수는 단순히 잘못된 입력이 들어왔음을 화면에 출력한 후, play 함수를 다시 호출합니다. play 함수를 다시 호출해서 입력부터 다시 시작하는 거죠.
  • 28. check check :: Int -> Int -> IO () check answer guess | answer == guess = do putStrLn "you are right!" end | otherwise = do let (s, b) = getStrikeAndBall answer guess putStrLn $ show s ++ " strike, " ++ show b ++ " ball" play answer check 함수는 정답을 맞췄을 경우, 아닐 경우 두 가지가 있습니다. 정답을 맞췄을 경우 정답임을 출력하고 종료, 그렇지 않을 경우 strike, ball 개수를 출력한 후 play 함수를 호출함으로써 다시 정답을 추론하게 만듭니다.
  • 29. getStrikeAndBall getStrikeAndBall :: Int -> Int -> (Int, Int) getStrikeAndBall answer guess = (strike, ball) where a = show answer g = show guess strike = length $ filter ((x,y) -> x == y) (zip a g) ball = (length $ filter (x -> x `elem` a) g) - strike getStrikeAndBall 함수는 추측 값과 정답이 주어졌을 때 strike, ball 개수를 튜플 형태로 반환합니다. 로직은 단순한 편이니 쉽게 이해할 수 있을거에요. strike는 answer와 guess를 zip한 후 튜플의 두 요소가 일치하는 개수를, ball은 guess의 자릿수중 answer에 포함되는 이들의 개수를 구한 후 거기서 strike 개수만큼 제외합니다.
  • 30. result 작성한 코드를 컴파일해서 실행해보면 다음과 같은 결과를 얻을 수 있습니다. guess the answer!(0 : exit) 123 0 strike, 1 ball guess the answer!(0 : exit) 111 invalid input. guess the answer!(0 : exit) 456 1 strike, 0 ball guess the answer!(0 : exit) 789 0 strike, 1 ball guess the answer!(0 : exit) 159 1 strike, 0 ball guess the answer!(0 : exit) 258 1 strike, 0 ball guess the answer!(0 : exit) 357 you are right! game over.
  • 31. more.. baseball 게임에 다음 요소들을 추가해봅시다. • 턴 수 출력 정답을 맞추는데 까지 몇 턴이 걸렸는지를 출력해봅시다. • 다시 시작 정답을 맞추면 프로그램을 종료하지 말고, 새로운 정답을 기반으로 다시 플레이를 하게 만들어봅시다.