자료구조

  • Vectors
  • Maps

데이터 묶음 - Collections

지금까지는 데이터 하나하나만을 다뤘습니다: 숫자, 문자열, 값. 프로그래밍시에는 데이터들의 묶음, 즉 콜렉션을 가지고 작업하는 경우가 더 많습니다.

클로저는 이런 collections와 함께 작업할 수 있는 훌륭한 기능들을 갖추고 있습니다. 클로저는 네 가지의 collections을 제공할 뿐만 아니라, 이 모든 collection들을 함께 사용하는 일관된 방법을 제공합니다.

Vectors

순서가 있는 콜렉션

벡터는 순서가 있는 값들의 모음입니다. 벡터는 비어있을 수 있으며, 서로 다른 타입의 값들을 담을 수 있습니다. 벡터의 값들은 순서를 매길 때에 0부터 세고, 이 숫자를 인덱스라고 합니다. 인덱스는 값들을 참조할 때 사용합니다.

객실과 비슷한 구조

벡터를 상상하기 위해서는 일정한 크기의 칸으로 나누어진 박스를 상상해 보세요. 각각의 칸은 수를 가지고 있습니다. 각각의 칸에는 데이터들을 넣을 수 있고 번호가 있기 때문에 어디서 그 데이터를 찾아야 할 지 알 수 있습니다.

그 숫자는 0부터 시작되한다는 것을 주의하세요. 이상하게 보일 수도 있지만 프로그래밍 할 때는 0부터 계산해야 합니다.

Vector

문법

벡터는 원하는 개수의 값들을 [] 안에 공백으로 구분하여 써서 사용합니다. 다음은 벡터에 관한 예제들 입니다:

[1 2 3 4 5]
[56.9 60.2 61.8 63.1 54.3 66.4 66.5 68.1 70.2 69.2 63.1 57.1]
[]

사용 예

한 쌍의 거북이들이 있을때, (turtle-name) 명령은 벡터의 형태로 거북이의 이름을 반환합니다.

(turtle-names)
;=> [:trinity :neo :oracle :cypher]

생성

다음 두 함수는 새로운 벡터를 만들 때 사용합니다. vector 함수는 임의의 개수의 인수들을 받고, 그것들을 모두 담은 벡터를 반환합니다. conj는 모든 콜렉션 자료형에 사용할 수 있는 흥미로운 함수입니다. 첫번쨰 인수가 벡터인 경우에는, 그 벡터의 맨 끝에 두번째 인수로 주어진 값을 추가한 새로운 벡터를 반환합니다. conj는 왜 이름이 conj일까요? conj는 join or combine을 의미하는 conjoin을 짧게 쓴 것입니다. 이것이 바로 우리가 하는 일입니다: 벡터에 새로운 값을 추가합니다.

(vector 5 10 15)
;=> [5 10 15]

(conj [5 10] 15)
;=> [5 10 15]

추출

다음 네개의 함수를 살펴봅시다. count는 벡터 안에 있는 값의 개수를 알려줍니다. nth는 벡터안의 n번째 값을 알려줍니다. 이때 0부터 세야한다는 것을 주의하세요. 따라서 이 경우에는 1과 함께 사용된 nth는 두번째 값(인덱스는 0부터 시작한다는 점에 주의!)을 알려줍니다. first는 벡터에서 첫번째 값을 반환해줍니다. rest는 첫번째 값을 제외한 나머지 모든 값들을 반환해 줍니다. 혼란스러울 수 있으므로 nth와 동시에 생각하지 않도록 하세요.

(count [5 10 15])
;=> 3
(nth [5 10 15] 1)
;=> 10
(first [5 10 15])
;=> 5
(rest [5 10 15])
;=> (10 15)

연습문제 1: 거북이 이름 확인하기

  1. 파일의 코드를 이용해서 거북이를 추가하세요.
    • walk.clj파일로 이동하세요.
    • walk.clj파일의 맨 끝에 (add-turtle :neo)를 추가하세요
    • 이 줄을 선택하고 “Reload Selection”을 클릭하세요
  2. (선택사항) REPL을 사용하여 거북이를 추가합니다.
    • (add-turtle :oracle)을 하단 REPL창에 입력하세요.
  3. 거북이 이름을 확인하세요.
    • 하단 REPL창에 (turtle-names)를 입력하고 결과를 확인하세요.

연습문제 2: 벡터 만들기

  • myprojectcore.clj로 이동해서 InstaREPL을 실행하세요.
  • 당신이 살고 있는 마을에서 다음 7일 간의, 일 최고 기온들로 이루어진 벡터를 만드세요.
  • 그런 다음 다음주 화요일의 일 최고 기온을 얻기 위해 nth함수를 사용하세요.

Maps

키와 값의 쌍들

맵은 키 값과 그에 관련된 value 값의 집합이 들어있습니다. 이는 사전을 생각하면 됩니다: 우리는 단어(keyworld)를 사용해서 뜻(value)을 찾아봅니다. 만약 다른 언어를 이용해서 프로그래밍한다면 dictionary, hash, associative array와 같은 이름으로 맵과 유사한 것을 볼 수 있을 것입니다.

Map

문법

맵은 중괄호 안에 키와 값을 교대로 써서 만듭니다.

맵은 우리가 일반적으로 생각하는 방식으로 데이터를 저장할 수 있기 때문에 유용합니다. Sally Brown을 예로 들어봅시다. 맵에 그녀의 성과 이름, 주소, 좋아하는 음식 등의 정보를 저장할 수 있습니다. 이는 해당 데이터를 모으고 보기 편하게 만드는 간단한 방법입니다. 마지막 예시는 비어있는 맵입니다. 이는 값을 저장할 준비는 되어있지만 아직 아무것도 없는 맵입니다.

{:first "Sally" :last "Brown"}
{:a 1 :b "two"}
{}

사용 예

거북이가 forwardright같은 명령을 받으면 맵의 맵 형태로 결과를 반환해 줍니다.

(forward 40)
;=> {:trinity {:length 40}}
(right 90)
;=> {:trinity {:angle 90}}

생성

assocdissoc은 한 쌍의 함수입니다: 이것들은 맵에서 항목들을 연결하거나 연결을 끊습니다. assoc을 이용해서 어떻게 성 “Brown”을 추가하고, dissoc을 이용해서 그것을 삭제하는지 알아봅시다. merge는 두 개의 맵을 합친 새로운 맵을 반환합니다.

(assoc {:first "Sally"} :last "Brown")
;=> {:first "Sally", :last "Brown"}

(dissoc {:first "Sally" :last "Brown"} :last)
;=> {:first "Sally"}

(merge {:first "Sally"} {:last "Brown"})
;=> {:first "Sally", :last "Brown"}

추출 1

count는 모든 collection에서 사용할 수 있는 함수입니다. 왜 답이 2라고 생각하나요? count는 키-값 쌍의 개수를 반환해 주기 때문입니다.

맵은 키-값의 쌍으로 이루어져있기 때문에, 키는 맵에서 값을 얻기 위해서 사용이 됩니다. 다음은 클로저에서 종종 사용되는 방법입니다. 맵에서 값을 찾기 위해서 키는 함수처럼 사용될 수 있습니다. 마지막 예에선, :MISS라는 키를 제공하고 있습니다. 이는 우리가 찾는 키값이 맵에 존재하지 않을 때 반환됩니다.

(count {:first "Sally" :last "Brown"})
;=> 2

(get {:first "Sally" :last "Brown"} :first)
;=> "Sally"
(get {:first "Sally"} :last)
;=> nil


(get {:first "Sally"} :last :MISS)
;=> :MISS

추출 2

keysvals라는 간단한 함수들이 있습니다: 맵에서 키들과 값들을 반환합니다. 순서는 보장할 수 없기 때문에 (:first :last)(:last :first)의 형태로 값이 반환될 것입니다.

(keys {:first "Sally" :last "Brown"})
;=> (:first :last)

(vals {:first "Sally" :last "Brown"})
;=> ("Sally" "Brown")

업데이트

맵을 생성한 후, 키에 새로운 값을 연결시키기를 원할 수 있습니다. assoc함수는 기존의 키에 새로운 값을 추가할 때 사용될 수 있습니다. 또, update라는 편리한 함수도 있습니다. update는 맵과 키를 함께 사용합니다. 지정된 키가 함수의 첫번 째 인자가 됩니다. update-in 함수는 update처럼 작동하지만 중첩된 맵의 경로에 서 업데이트하기 위해 키의 벡터가 인수로 필요합니다.

(def hello {:count 1 :words "hello"})

(update hello :count inc)
;=> {:count 2, :words "hello"}
(update hello :words str ", world")
;=> {:count 1, :words "hello, world"}


(def mine {:pet {:age 5 :name "able"}})

(update-in mine [:pet :age] - 3)
;=> {:pet {:age 2, :name "able"}}

Collections of Collection

수, 키워드, 문자열과 같은 값들만이 콜렉션에 넣을 수 있는 것은 아닙니다. 다른 콜렉션들도 콜렉션에 넣을 수 있으므로 맵의 벡터, 벡터의 리스트와 같이 데이터에 맞는 조합을 가질 수 있습니다.

Vector of Maps

(state-all)
;=> [{:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996, :angle 90, :color [106 40 126]}}
{:neo {:x 21.213202971967114, :y 21.213203899225725, :angle 45, :color [0 64 0]}}
{:oracle {:x -49.99999999999981, :y -4.3711390001862375E-6, :angle 180, :color [43 101 236]}}]

(def states (state-all))
;=> #'clojurebridge-turtle.walk/states

(first states)
;=> {:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996,
:angle 90, :color [106 40 126]}}

Map of Maps

(def st (first states))
;=> #'clojurebridge-turtle.walk/st

st
;=> {:trinity {:x -1.7484556000744965E-6, :y 39.99999999999996,
;=>            :angle 90, :color [30 30 30]}}

(get st :trinity)
;=> {:x -1.7484556000744965E-6, :y 39.99999999999996,
;=>  :angle 90, :color [30 30 30]}

(get-in st [:trinity :angle])
;=> 90

연습문제 3: 거북이들의 상태 확인하기

  • walk.clj 파일로 이동하세요.
  • 앞의 두 슬라이드의 예제를 REPL에서 실행하세요.
  • 얻은 값을 확인하세요

REPL에서 코드를 입력할 때 __enter__를 잊지 마세요.

(state-all)
(def states (state-all))
(first states)
(def st (first states))
st
(get st :trinity)
(get-in st [:trinity :angle])

연습문제 4: 당신을 모델링하세요

  • myprojectcore.clj와 InstaREPL을 사용하세요.
  • 당신을 표현하는 맵을 만드세요.
  • 이 맵에는 당신의 성과 이름이 들어있어야 합니다.
  • assoc이나 merge.를 사용해서 맵에 당신의 고향을 추가하세요.

연습문제 5 [보너스]: 친구를 모델링하세요

  • 첫번째로, 앞의 excercise에서 만든 당신에 대한 맵을 가져오세요.
  • 당신 주위에 있는 두세명의 친구의 성, 이름, 고향을 담고있는 맵 벡터를 만드세요.
  • 마지막으로 conj을 사용해서 당신의 맵을 그들의 정보에 추가하세요.

첫번째 슬라이드로 돌아가거나 curriculum outline으로 가세요.