haskell study 2

27
Haskell Study 2. list & tuple

Upload: nam-hyeonuk

Post on 17-Jan-2017

1.261 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Haskell study 2

Haskell Study

2. list & tuple

Page 2: Haskell study 2

List - 표기

list는 Haskell에서 가장 많이 사용되는 자료구조입니다. List는 동일한 타입의 자료를 담는

자료구조이며, 타 언어에서의 링크드 리스트(linked list)와 동일한 내부구조를 갖고 있다고

생각하시면 됩니다. 리스트는 대괄호([]) 및 콤마(,)를 이용하여 표현합니다. ghci를 켜서

확인해봅시다. ghci에서 이름(함수)을 정의하려면 let 키워드를 사용해야합니다. ghci에서 let a = 3

이라고 쓰는 건 스크립트 파일에서 a =3 이라고 쓰고 ghci에서 그 파일을 읽어오는 것과 동일합니다.

Prelude> let numbers = [1,3,5,10,12]Prelude> numbers[1,3,5,10,12]Prelude> let evens = [2,4,6,8,10,12]Prelude> evens[2,4,6,8,10,12]

Page 3: Haskell study 2

List - 표기

Haskell에서 문자열은 문자들의 리스트와 동일합니다. 각 문자는 홑따옴표(‘) 사이의 문자로

표현되며 문자열은 쌍따옴표(“)사이의 문자들로 표현됩니다. [‘1’,’2’,’3’]과 “123”은 같은

표현입니다.

Prelude> [‘1’,’2’,’3’]“123”Prelude> “123”“123”

Page 4: Haskell study 2

List - 표기

두 개의 서로 다른 리스트를 합칠 때는 ++ 연산자를 사용합니다. ++ 연산자를 사용할 때 주의할 점은

이 연산자의 수행 속도가 굉장히 느리다는 것입니다. Haskell의 list는 내부적으로 linked list처럼

동작하기 때문에, ++ 연산자를 사용하면 연산자의 좌측에 있는 모든 원소를 한 번씩 순회하게 됩니다.

리스트에 원소를 추가할 때 사용하는 다른 연산자로 : (cons) 연산자가 있습니다. 이 연산자는

리스트의 맨 앞에 원소 하나를 추가할 때 사용됩니다.

Prelude> “1234” ++ “5678”“12345678”Prelude> 0:[1,2,3][0,1,2,3]

* 참고) 리스트 [0,1,2,3]은 0:1:2:3:[]과 같은 것입니다.

Page 5: Haskell study 2

List - 표기

리스트의 리스트같은 것도 얼마든지 가능합니다. 단, 리스트는 같은 타입의 원소만 담을 수

있으므로 리스트의 리스트같은 경우 내부 리스트들의 타입이 서로 동일해야합니다. 길이는 달라도

상관없습니다.

Prelude> [ [1,2,3] , [1,2] , [5,6,7] ][[1,2,3],[1,2],[5,6,7]]Prelude> [ “apple”, “banana” ][“apple”, “banana” ]Prelude> [ [1,2,3], “apple” ]<interactive>:4:3: No instance for <Num Char> arising from the literal ‘1’ In the expression : 1 In the expression : [1,2,3] In the expression : [[1, 2, 3], “apple”]

서로 타입이 다른 리스트를 한

리스트에 넣을 경우 이렇게 에러

메시지가 뜹니다

Page 6: Haskell study 2

List - 비교

같은 타입의 두 리스트는 서로 비교할 수 있습니다. 물론 리스트의 원소 타입이 서로 비교될 수 있는

타입이어야합니다. 리스트의 비교의 경우 사전순으로 이루어집니다. 첫번째 원소끼리 비교해서 큰

쪽이 더 크고, 첫번째 원소가 서로 같다면 두번째 원소끼리 비교해서 큰 쪽이 더 크고, ... 하는 식이죠.

Prelude> [1,2,3] > [4,5,6]FalsePrelude> [1,2,3] > [1,2]TruePrelude> [3,4,2] > [2,4]TruePrelude> [3,4,2] == [3,4,2]True

Page 7: Haskell study 2

List - 연산

리스트에서 특정 인덱스에 있는 원소의 값을 가져 오고 싶을 때는 !! 연산자를 사용합니다.

Prelude> [1,2,3,4] !! 23

head는 리스트의 맨 첫 번째 원소를, tail은 맨 첫 번째 원소를 제외한 나머지 부분의 리스트를

돌려주는 함수입니다.

Prelude> head [1,2,3,4]1Prelude> tail [1,2,3,4][2,3,4]

Page 8: Haskell study 2

List - 연산

last 함수는 리스트의 맨 마지막 원소를, init 함수는 리스트의 마지막 원소를 제외한 나머지 원소들의

리스트를 돌려주는 함수입니다.

Prelude> last [1,2,3,4]4Prelude> init [1,2,3,4][1,2,3]

아래 그림처럼 생각하시면 편합니다.

[1,2,3,4,5,6,7,8,9,10]head tail

lastinit

Page 9: Haskell study 2

List - 연산

length는 리스트의 길이를 돌려줍니다.

Prelude> length [1,2,3,4]4

null은 리스트가 텅 비었는지 아닌지 확인해주는 함수입니다. list == [] 와 같은 조건문 보다는 이

함수를 쓰는 편이 편합니다.

Prelude> null [1,2,3,4]FalsePrelude> null []True

Page 10: Haskell study 2

List - 연산

reverse는 리스트를 거꾸로 뒤집어줍니다.

Prelude> reverse [1,2,3,4][4,3,2,1]

take는 숫자와 리스트를 인자로 받아 리스트의 시작점부터 해당 개수만큼의 원소로 이루어진

리스트를 반환합니다.

Prelude> take 3 [1,2,3,4][1,2,3]Prelude> take 5 [1,2,3,4][1,2,3,4]

Page 11: Haskell study 2

List - 연산

drop은 반대로 주어진 개수만큼의 원소를 리스트의 시작점부터 제거합니다.

Prelude> drop 3 [1,2,3,4][4]Prelude> drop 5 [1,2,3,4][]

maximum / minimum은 각각 리스트의 최댓값과 최솟값을 반환합니다.

Prelude> maximum [1,2,3,4]4Prelude> minimum [1,2,3,4]1

Page 12: Haskell study 2

List - 연산

sum / product는 각각 리스트의 원소들 모두를 더한 값과 모두를 곱한 값을 반환합니다.

Prelude> sum [1,2,3,4]10Prelude> product [1,2,3,4]24

elem은 원소 하나와 리스트를 받아서 해당 원소가 리스트에 포함되는지 아닌지 여부를 판별해줍니다.

Prelude> ‘a’ `elem` “abcd”TruePrelude> 3 `elem` [1,2,4,5]False

Page 13: Haskell study 2

List - 범위

Haskell에는 특정 범위에 있는 값들의 리스트를 만들고 싶을 때 사용할 수 있는 좋은 문법이 있습니다.

범위(range) 리스트는 특정 범위에 있는 등차 수열을 쉽게 만들어냅니다.

Prelude> [1..10][1,2,3,4,5,6,7,8,9,10]

기본적으로 [(시작값)..(종료값)] 형태의 문법을 갖고 있으며 이런 형태의 리스트는 시작값에서 값을

하나씩 증가시키며 종료값까지의 모든 원소들을 포함하는 리스트를 생성합니다.

Prelude> [‘a’..’z’]“abcdefghijklmnopqrstuvwxyz”Prelude> [‘A’..’F’]“ABCDEF”

Page 14: Haskell study 2

List - 범위

값의 증가 정도를 바꾸고 싶다면 첫번째 원소와 두번째 원소까지 표기해주면 됩니다. 이렇게 할 경우

두 원소의 차이만큼 값을 증가시키며 주어진 범위 내의 원소들로 이루어진 리스트를 만들어줍니다.

Prelude> [1,4..10][1,4,7,10]Prelude> [‘a’,’c’..’z’]“acegikmoqsuwy”Prelude> [0.1, 0.3 .. 1][0.1,0.3,0.5,0.7,0.899999999999,1.0999999999999]

부동 소수점의 경우 위 예제처럼 부정확한 결과가 나올 수 있으므로 이 문법에서는 부동 소수점은

되도록 안 쓰는 것이 좋습니다.

Page 15: Haskell study 2

List - 범위:무한

Haskell은 지연 계산이라는 특징을 이용해서 무한대 크기의 리스트를 만들어낼 수 있습니다. 앞의

범위 리스트에서 마지막 값을 생략하면 곧 무한대 크기의 리스트가 됩니다. 예를 들어 짝수들의

리스트를 생각해봅시다.

Prelude> let evens = [2,4..]

이제 evens는 짝수들의 리스트입니다. 이렇게 evens를 정의하는 시점까지는 지연 계산의

특성때문에 해당 식을 계산하지 않습니다. 이제 처음부터 10개의 짝수를 가져와봅시다.

Prelude> take 10 evens[2,4,6,8,10,12,14,16,18,20]

무한대 크기의 리스트는 유용하게 사용될 수 있습니다. 무한대 리스트와 관련된 함수를 살펴봅시다.

Page 16: Haskell study 2

List - 범위:무한

cycle 함수는 리스트를 인자로 받아 그 리스트의 원소를 무한히 반복하는 리스트를 만들어냅니다.

Prelude> take 10 (cycle [1,2,3])[1,2,3,1,2,3,1,2,3,1]Prelude> take 12 (cycle “EE! “)“EE! EE! EE! “

repeat는 원소를 하나 받아서 그 원소로만 이루어진 무한대 크기의 리스트를 만듭니다.

Prelude> take 5 (repeat 3)[3,3,3,3,3]

take n (repeat k)는 replicate n k와 같습니다. 위 예제의 경우 보통 replicate 5 3과 같이 씁니다.

Page 17: Haskell study 2

List Comprehension

list comprehension은 list를 만들 때 굉장히 유용하게 사용될 수 있는 문법입니다. 이 문법은

수학에서 집합을 표기할 때 사용하는 조건제시법과 아주 비슷합니다. 가장 처음 10개의 짝수를

나타내는 리스트를 list comprehension을 이용해 표기해봅시다.

Prelude> [x*2 | x <- [1..10]][2,4,6,8,10,12,14,16,18,20]

list comprehension에서 파이프(|) 왼쪽은 결과 리스트에 들어갈 원소를 나타내고, 파이프

오른쪽은 그 원소를 구하기 위한 여러가지 조건들을 의미합니다. 위 코드에서 x <- [1..10]을 통해 x

는 리스트 [1..10] 속에 속하는 원소임을 나타내고, 그 각각의 x에 대해 2를 곱한 값이 결과 리스트에

포함된다고 표기했으므로 최종적으로 첫 10개의 짝수 리스트를 얻게 됩니다.

Page 18: Haskell study 2

List Comprehension

몇 가지 예제를 더 살펴봅시다. 우선, 첫 10개의 짝수 중에서 크기가 10이상인 짝수들의 리스트를

list comprehension을 통해 구해봅시다. 이를 위해서 파이프(|) 오른쪽에 조건(술어)를 더해줄 수

있습니다.

Prelude> [x*2 | x <- [1..10], x*2 >= 10][10,12,14,16,18,20]

1부터 20 사이의 숫자중 3 또는 5의 배수는 어떻게 구할 수 있을까요?

Prelude> [x | x <- [1..20], x `mod` 3 == 0 || x `mod` 5 == 0][3,5,6,9,10,12,15,18,20]

Page 19: Haskell study 2

List Comprehension

list comprehension에서 술어를 여러 개 줄 수도 있으며, 한 번에 여러 개의 리스트로부터 원소를

가져올 수도 있습니다. 예를 들어서, [1,2,3]과 [4,5,6]의 모든 원소들의 곱으로 이루어진 원소를

만들고 싶다고 해 봅시다.

Prelude> [x*y | x <- [1,2,3], y <- [4,5,6] ][4,5,6,8,10,12,12,15,18]Prelude> [x*y | x <- [1,2,3], y <- [4,5,6], x*y > 10][12,12,15,18]

이건 마치 명령형 언어에서 이중 반복문처럼 동작합니다. [1,2,3]에서 원소들을 하나씩 꺼내고, 그

원소 각각에 대해 y의 모든 원소들을 대응시키면서 모든 경우의 수를 테스트해봅니다.

Page 20: Haskell study 2

List Comprehension

list comprehension 역시 중첩해서 사용할 수 있습니다. 예를 들어 list의 list에서 내부의 list들에

대해 짝수인 원소들은 모두 제거하고 싶다고 해 봅시다.

first.hsremoveOdd xxs = [ [ x | x <- xs, odd x] | xs <- xxs]

ghciPrelude> :l first.hsPrelude> removeOdd [[1,2,3,4], [5,6,7,8], [2,4,6,8], [1,3,5,7]][[1,3],[5,7],[],[1,3,5,7]]

list comprehension은 중첩해서 쓸 경우 가독성이 별로 좋지 않습니다. 이럴 땐 여러 줄에 나눠서

쓰거나 다른 방식으로 문제를 해결하는 걸 고민해보시는 게 좋습니다.

Page 21: Haskell study 2

연습 문제

• lastButOnelist가 주어지면 해당 리스트의 마지막에서 한 칸 앞 원소를 리턴하는 함수를 작성해봅시다. 예를 들어

lastButOne [1,2,3,4]는 3을 리턴해야합니다.

• notCapital문자열이 주어졌을 때 해당 문자열에서 대문자를 제거한 문자열을 리턴하는 함수를 작성해봅시다.

예를 들어 notCapital “HaHaHa, NotCapital” 은 “aaa, otapital”을 리턴해야합니다.

• diff두 개의 리스트 a,b가 주어졌을 때 a의 원소 중 b에 포함되지 않는 원소로만 구성된 리스트를 리턴하는

함수를 작성해봅시다. 예를 들어 diff [1,2,3,4,5,6] [3,5,7] 은 [1,2,4,6]을 리턴해야합니다.

Page 22: Haskell study 2

tuple

tuple은 list와 비슷하게 여러 개의 값을 담는 자료구조입니다. tuple이 list와 차이 나는 두 가지는,

tuple은 원소 개수가 고정이며 서로 다른 타입의 값도 담을 수 있다는 것입니다. tuple은 소괄호와

콤마로 구성됩니다. 튜플은 수학에서 벡터 등을 표기하는데 굉장히 유용하게 사용될 수 있습니다.

튜플의 한 가지 특징은 원소 개수가 다르면 서로 다른 타입이라는 것입니다.

Prelude> [(1,2), (1,2,3)]<interactive>:2:9: Couldn’t match expected type ‘(t, t3)’ with actual type ‘(t0, t1, t2)’ Relevant bindings include it :: [(t, t3)] (bound at <interactive>:2:1) In the expression: (1,2,3) In the expression: [(1,2), (1,2,3)] In an equation for ‘it’ : it = [(1,2), (1,2,3)]

이렇게 서로 다른

타입을 한 리스트에

담을 수 없다는 에러

메시지가 발생합니다.

Page 23: Haskell study 2

tuple

튜플은 수학에서의 2차원, 3차원 벡터 등을 표기할 때나, 서로 다른 타입의 값 몇 가지를 묶어서

관리할 필요가 있을 때 굉장히 유용하게 사용됩니다. 일반적으로 크기가 2인 튜플을 페어(pair), 3인

튜플을 트리플(triple)이라고 부릅니다. 페어의 경우 fst와 snd 함수가 유용합니다.

Prelude> fst (1,4)1Prelude> snd (1,4)4

fst와 snd 함수는 크기가 2인 튜플에서만 사용가능합니다. 3-tuple, 4-tuple, ... 등에 대해서는

동작하지 않습니다. 이런 다양한 크기의 튜플에 대해 각 원소들을 뽑아내는 방법은 다음에 다뤄봅시다.

Page 24: Haskell study 2

tuple

pair들의 리스트를 만들어내는 zip이라는 굉장히 유용한 함수가 있습니다. 이 함수는 두 개의

리스트를 인자로 받아 두 리스트의 원소를 하나씩 짝 지은 페어들의 리스트를 반환합니다.

Prelude> zip [‘one’, ‘two’, ‘three’] [1,2,3][(‘one’,1), (‘two’,2), (‘three’,3)]Prelude> zip [1..] “abcd”[(1,’a’), (2,’b’), (3,’c’), (4,’d’)]

zip은 두 개의 리스트를 하나로 합치거나 두 리스트를 동시에 순회해야할 때 유용하게 사용할 수

있습니다. zip의 인자로 들어온 두 리스트의 길이가 서로 다를 경우 길이가 짧은 쪽의 리스트에 맞춰서

잘립니다.

Page 25: Haskell study 2

활용

tuple과 list를 이용해서 다음 문제를 풀어봅시다.

“세 변의 길이가 모두 10이하의 정수인 삼각형 중에서, 둘레가 24인 직각 삼각형에는 어떤 것이

있는가?”

이 문제는 list comprehension과 tuple을 이용하면 굉장히 쉽게 풀 수 있습니다. 우선 list

comprehension을 이용해 세 변의 길이가 모두 10이하인 삼각형 목록을 구해봅시다.

[(a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10]]

Page 26: Haskell study 2

활용

여기서 직각 삼각형의 경우 빗변 길이의 제곱은 나머지 두 변의 길이의 제곱의 합과 같으며,

직각삼각형의 각 변은 빗변의 길이보다 길 수 없다는 조건을 반영해봅시다.

[(a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2]

이제 우리가 고려하지 않은 조건은 둘레가 24라는 조건 하나뿐입니다. 이 조건 하나까지 반영해서

답을 구해 봅시다.

Prelude> [(a,b,c)| c <- [1..10], b <- [1..c], a <- [1..b],a^2 + b^2 == c^2, a+b+c==24][(6,8,10)]

Page 27: Haskell study 2

연습 문제

• swap페어의 리스트를 받아서 리스트의 모든 원소를 순서를 바꾸는 함수를 만들어봅시다. 예를 들어

swap [(1,2), (3,4), (5,6), (7,8)]은 [(2,1), (4,3), (6,5), (8,7)]을 리턴해야합니다.

• sum리스트를 받아서 리스트의 모든 원소의 합을 구하는 함수를 만들어봅시다. 이미 표준 라이브러리에

존재하는 함수니 sum 외의 다른 이름으로 작성하셔야합니다. 예를 들어 sum’ [1,2,3,4,5] 는 15를

리턴해야합니다.

• divisors숫자 하나를 받아 그 숫자의 모든 약수 리스트를 리턴하는 함수를 만들어봅시다. 예를 들어

divisors 20 은 [1,2,4,5,10,20]을 리턴해야합니다.