2
4
0 0
0 1
10
1 1
3
3 0
2 7
4 8
• 테스트 케이스 개수
• 케이스 1
• 데이터 개수
• 데이터 1 (좌표)
• 데이터 2
• …
• 데이터 N
• 케이스 2
• 데이터 개수
• 데이터 1
• 데이터 2
• …
전형적인 입력 형식
1. 입출력
4.
import Control.Monad
main =do
numCases <- read `fmap` getLine :: IO Int
-- numCases <- readLn :: IO Int
replicateM_ numCases solve
solve = do
…
• replicateM_ 필요
• 정수 읽기 (테스트 케이스 개수 T)
• 또는 이렇게
• 문제 풀이를 T번 실행
• 실제 문제 푸는 코드
1. 입출력
대부분의 코딩은 이렇게 시작
5.
입력 파싱
• 수하나
• n <- read `fmap` getLine :: IO Int
• x <- read `fmap` getLine :: IO Double
• 띄어쓰기로 구분된 수 목록
• ns <- (map read . words) `fmap` getLine :: IO [Int]
• 줄바꿈으로 구분된 좌표 목록
• n <- read `fmap` getLine :: IO Int
• ps <- replicateM n $ do
[x,y] <- (map read . words) `fmap` getLine :: IO [Int]
return (x,y)
1. 입출력
• 입력: 10
• n = 10
• x = 10.0
• 입력: 1 2 3 4
• ns = [1, 2, 3, 4]
• 입력: 3
0 1
3 3
2 5
• ps = [(0,1),(3,3),(2,5)]
6.
calc xs =f xs acc where
f [] acc = acc
f (x:xs) acc = f xs (acc + g x) where
g x = …
feasible candies = f (length candies) candies 0 where
f _ [] _ = True
f i (x:xs) acc
| (x+acc) `mod` i /= 0 = False
| otherwise = f (i-1) xs (acc + div (acc + x) i)
• 누적 변수(accumulator)가 유용하다
• 코딩이 쉬워진다
• 컴파일러가 최적화하기 좋다
• 실제 예시
2. 재귀
재귀 함수 코딩하기
7.
임의 개수의 결과를반환하는 비결정적 계산
• 뭔 소리야
• 컴퓨터 과학 시간이 아니다
• 그냥 문제 풀 때는 경우의 수 생성기로 생각
• 격자 생성
• 1 <= x <= 10, 1 <= y <= 10 격자점들
• grid = [ (x,y) | y <- [1..10], x <- [1..10]]
• 모델링하기 좋은 계산
• 초기 리스트에 반복적으로 map 적용, concat으로 연결
• 상태 공간 트리 탐색
3. 리스트 모나드
8.
랭포드 쌍의 생성
•랭포드 쌍의 정의
• {1,1,2,2,…,n,n}의 모든 두 x에 대해 x와 x 사이에 다른 수가 x개 오는 배치
• {1,1,2,2,3,3} -> 2 3 1 2 1 3
• {1,1,2,2,3,3,4,4} -> 4 1 3 1 2 4 3 2
• 구현 방법
• 빈 배치로 시작
• 가능한 모든 위치에 두 n을 놓는다
• 남은 가능한 모든 위치에 두 n-1을 놓는다
• …
• 남은 가능한 모든 위치에 두 1을 놓는다
• 상태 공간 트리
• 탐색 순서에 따라 백트래킹, 레벨 우선 순회, 가지치기하면 분기 한정(branch and bound) 등…
3. 리스트 모나드
9.
리스트 모나드를 이용한레벨 우선 순회
langford2 n = f [n,n-1..1] [] where
f :: [Int] -> [(Int,Int)] -> [[(Int,Int)]]
f [] alignment = return alignment
f (x:xs) alignment = do
pos <- possiblePositions x n alignment
let align' = (pos,x) : (pos+x+1,x) : alignment
soln <- f xs align'
return soln
possiblePositions x n alignment = [i | i <- [1..2*n-x-1], notElems (i,i+x+1)] where
notElems (a,b) = all ((i,_) -> i /= a && i /= b) alignment
3. 리스트 모나드
10.
import Data.List
main =do
xs <- words `fmap` getLine :: IO [String]
minimumBy comp (permutations xs)
comp :: String -> String -> Ord
comp x y = …
• minimumBy, permutations 함수
• 문자열 10개의 리스트
• 순열들의 지연 리스트
• minimumBy로 최소 원소 검색
• 그럼 공간 복잡도는 상수겠네?
4. 지연 평가와 가비지 컬렉션
10!개의 순열
11.
공간 복잡도 상수아닌데요
• 왜요
• 놀랍게도 GHC 탓
• minimumBy는 평가가 끝날 때까지 리스트 전체를 메모리에 붙잡아둔다
• 왜 그러냐고? 저도 모르겠어요
• 즉 이미 비교가 끝난 원소를 가비지 컬렉션하지 못한다
• 길이 10!이고 완전 평가된 리스트 = 메모리 한계 초과
• 최소 원소 구하는 함수를 직접 정의. 가비지 컬렉션 잘됨
least (x:xs) = f x xs where
f x [] = x
f x (y:ys) = case comp x y of
LT -> f x ys
_ -> f y ys
4. 지연 평가와 가비지 컬렉션
12.
문자열 데이터 타입
•문자열 = String = [Char] 아닌가
• 아닌데요
• 입력이 몇 개 없거나 처리할 양이 적으면 무관
• 문자열 대규모 입출력시 성능이 심각하게 구리다
• String을 썼는데 메모리 or 시간 초과하면
• Text 또는 ByteString 검토
• 일반적인 문자열 처리는 Text
• Ascii 문자만 쓰면 ByteString이 제일 빠르다
5. 문자열 ≠ String
13.
아주 긴 문자열입력
• String으로 읽은 다음 변환
solve = do
ns <- getLine
let candies = (map read . words) ns :: [Int]
• 그런데 숫자는 최대 10만개, 각 숫자는 최대 10글자
• 띄어쓰기로 구분하면 최대 110만 글자 정도
• 110만 글자 = 1100000 Bytes = 1.1 MB 뭐가 문제?
5. 문자열 ≠ String
14.
String의 메모리상 표현
•단방향 링크드 리스트
• 문자열의 한 문자당 5 워드 = 40 바이트 (1워드 = 8바이트 가정)
• 110만 글자 = 1.1 * 40 MB = 44 MB
• 잉?
5. 문자열 ≠ String
15.
String 대신 ByteString
•ByteString은 메모리상에서 C의 char[] 배열과 유사하게 표현된다
• String 읽는 코드를 다음과 같이 대체
import Data.Maybe
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
solve = do
ns <- BS.getLine
let candies = (map (fst . fromJust . BS8.readInt) . BS8.words) ns
• 실행 속도가 3배 빨라진다
5. 문자열 ≠ String