User Tools

Site Tools


lecture:4clojure:level-elementary

Elementary/Easy 레벨 문제풀이를 위한 Clojure 문법

키워드 자료형

키워드(Keyword)는 Java의 Enum 과 같은 개념으로 생각하면 된다. 즉 임의의 상수이다. :으로 시작하는 임의의 문자열은 키워드이다.

:a 
:1 
:@

Boolean과 Equality

Clojure에서 참/거짓을 나타내는 논리형 데이타는 true와 false 이다.

(= 1 1) ;==> true
(= 1 2) ;==> false
 
(if true 1 2) ;==> 1
(if false 1 2) ;==> 2
(= true (not false)) ;==> true

false 과 nil 이외의 모든 값은 참이다.

(if false 1 2) ;==> 2
(if nil 1 2) ;==> 2
 
(if 0 1 2) ;==> 1    0은 숫자
(if 1 1 2) ;==> 1    1은 숫자
(if "" 1 2) ;==> 1   "" empty string
(if \A 1 2) ;==> 1   \A 는 문자(Character)
(if [] 1 2) ;==> 1   [] empty vector

Java Interop

Clojure는 Java 코드를 그대로 가져다 사용할 수 있는 방식을 제공하는데, 이것을 Java Interop이라고 한다.

Clojure의 문자는 Java의 Character 인스턴스에, 스트링은 Java의 String 인스턴스이다.

Java 오브젝트의 메소드를 다음과 같이 호출할 수 있다.

(.toUpperCase "abc") ;==> "ABC"

클래스의 정적 메소드는 다음과 같이 호출할 수 있다.

(Character/isUpperCase \A) ;==> true

함수 호출

Clojure는 Lisp이기 때문에 괄호를 사용한다.

괄호의 첫 요소는 함수이고, 나머지는 파라미터이다.

(str "Hello " "World!") ;==> "Hello World!"

따라서 수치연산 함수는 전위연산자이다.

(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 1 2) ; => 2
(/ 2 1) ; => 2
 
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2

익명 함수

(fn [s] (println "hello, " s))
#(println "hello, " %)
 
(#(* % %) 5) ;==> 25
((fn [n] (* n n)) 5) ;==> 25
 
(#(* %1 %2) 5 4) ;==> 20
((fn [x y] (* x y)) 5 4) ;==> 20

자료 구조

Clojure에는 4개의 자료 구조가 있다 : List, Vector, Set, Map

(1 2 3)  ; 리스트는 ()로 묶는다. 순서가 있으며 index를 통한 임의 접근이 않된다.
[1 2 3]  ; 벡터는 []로 묶는다. 순서가 있으며 index를 통한 임의 접근이 된다.
#{1 2 3} ; 집합은 #{}로 묶는다. 순서가 없으며 value를 통한 접근이 된다. value는 중복값을 가질 수 없다.
{:a 1 :b 2 :c 3}  ; 맵은 {}로 묶는다. 순서가 없으며 key를 통한 접근이 된다. key는 중복값을 가질 수 없다.

리스트를 제외한 나머지 자료구조는 인덱스나 키로 요소를 가져올 수 있다.

; 벡터는 인덱스로 값을 가져온다.
([1 2 3] 0) ;=> 1 ; 벡터의 첫 요소는 0는 인덱스 번호 0이다.
([1 2 3] 3) ;=> nil ; 인덱스 번호 3은 네번째 요소로 존재하지 않는다.
 
; 맵은 키로 값을 가져온다.
({:a 1 :b 2 :c 3} :a) ;=> 1   ;=> :a키에 해당하는 값은 1이다.
({:a 1 :b 2 :c 3} :d) ;=> nil ;=> :d라는 키는 없기 때문에 nil을 리턴
({:a 1 :b 2 :c 3} :d 4) ;=> 4 ;=> 맵에 해당키가 없는 경우 디폴트 값을 지정할 수 있다.
 
; 집합은 요소 자신을 통해 값을 가져온다. (결국 존재여부 확인)
(#{1 2 3} 1) ;=> 1  ; 집합은 값과 키가 같은 맵이라고 생각하면 된다. 앞의 집합은 {1 1 2 2 3 3} 맵으로 생각할 수 있다.
(#{1 2 3} 0) ;=> nil ; 앞의 집합에서 1은 있으나 0은 없다.

get 함수를 통해 명시적으로 요소를 가져올 수 있다.

(get [1 2 3] 0) ;=> 1 ; 인덱스는 0부터 시작한다.
 
(get {:a 1 :b 2 :c 3} :a) ;=> 1   
(get {:a 1 :b 2 :c 3} :d) ;=> nil 
(get {:a 1 :b 2 :c 3} :d 4) ;=> 4 ; 4는 디폴트 값.
 
(get #{1 2 3} 1) ;=> 1  
(get #{1 2 3} 0) ;=> nil

get 함수를 통하면 벡터와 집합에 해당 요소가 없을 경우 디폴트 값을 지정할 수 있다.

(get [1 2 3] 3) ;=> nil
(get [1 2 3] 3 100) ;=> 100
 
(get #{1 2 3} 0) ;=> nil
(get #{1 2 3} 0 100) ;=> 100

맵에서 해당키의 값이 nil일 경우, contains?로 키의 존재 여부를 확인할 수 있다.

({:a nil} :b) => nil
({:a nil} :a) => nil
(contains? {:a nil} :a) ;=> true

벡터는 nth로도 값을 가져올 수 있다.

(nth [1 2 3] 0) ;=> 1
(nth [1 2 3] 3) ;=> 예외(IndexOutOfBoundsException) 발생
(nth [1 2 3] 3 3) ;=> 3

자료구조에 추가/삭제 : cons/conj/pop/disj/assoc/dissoc/assoc-in

리스트에 추가

(cons 0 '(1 2 3)) ;=> (0 1 2 3)
(conj '(1 2 3) 4) ;=> (4 1 2 3)
(pop '(1 2 3)) ;=> (2 3)

Clojure는 코딩시 (1 2 3) 하면 위에서 함수를 설명한 것처럼 괄호()의 첫 요소인 1을 함수로 평가하게 되는데, 1은 함수가 아니기 때문에 예외가 발생한다. Clojure 컴파일러가 괄호()를 평가하지 않고 그냥 리스트로 인식하게 하기 위해서는 '(1 2 3) 처럼 하면 된다.

cons는 새로운 리스트를 만든다. 새로운 리스트의 첫 요소는 첫번째 파라미터이고, 나머지는 두번째 파라미터의 요소들이다.

conj(oin)는 리스트에 새 요소를 추가하는데, 리스트에 추가하는 가장 빠른 방법은 앞에 추가하는 것이다.

벡터에 추가

(cons 0 [1 2 3]) ;==> (0 1 2 3)
(conj [1 2 3] 4) ;==> [1 2 3 4]
(pop [1 2 3]) ;=> [1 2]

cons는 그 대상이 벡터여도 결과는 같다. 즉 맨 앞에 추가한 리스트를 리턴한다.

conj는 벡터일 때는 맨 뒤에 추가한다. 벡터는 인덱스 접근이 가능하기 때문이다.

집합에 추가/삭제

(conj #{1 2 3} 4) ;=> #{1 2 3 4} ; 추가
(disj #{1 2 3} 3) ;=> #{1 2}     ; 삭제

맵에 추가/삭제

(assoc {:a 1} :b 2) ;=> {:a 1 :b 2} ; 추가
(assoc {:a 1} :b 2 :c 3 :d 4) ;=> {:a 1 :b 2 :c 3} ; 추가
(dissoc {:a 1 :b 2} :b) ;=> {:a 1}  ; 삭제

assoc-in / update-in

(assoc-in {:a {:x 1}} [:a :x] 100) ;=> {:a {:x 100}}
(update-in {:a {:x 1}} [:a :x] inc) ;=> {:a {:x 2}}

자료 구조에 접근하는 함수 : first/second/last/rest

(first '(1 2 3 4 5)) ;=> 1
(second '(1 2 3 4 5)) ;=> 2
(last '(1 2 3 4 5)) ;=> 5
(rest '(1 2 3 4 5)) ;=> (2 3 4 5)

ffirst/next/butlast/fnext/nfirst

(ffirst [[1] [2]]) ;=> 1 ;(first (first x)) 와 같다
(next [1 2 3 4 5]) ;=> (2 3 4 5) ;rest와 같다. 마지막 요소가 없으면 nil을 리턴
(next []) ;=> nil
(butlast [1 2 3 4 5]) ;=> (1 2 3 4) ;마지막을 제외한 리스트
(fnext [1 2 3]) ;=> 2 ;(first (next x)) 와 같다.
(nfirst [[1 2] 3]) ;=> 2 ;(next (first x)) 와 같다.

rest와 next의 차이

(rest [1 2 3]) ;=> (2 3)
(next [1 2 3]) ;=> (2 3)
 
(rest []) ;=> ()
(next []) ;=> nil

take/drop

(take n coll) coll의 처음 n개의 요소로 된 lazy-seq를 리턴한다.

(take 3 '(1 2 3 4 5 6)) ;=> (1 2 3) ; 리스트를 받아 lazy-seq를 리턴
(take 3 [1 2 3 4 5 6) ;=> (1 2 3)   ; 벡터를 받아 lazy-seq를 리턴
(take 3 [1 2]) ;=> (1 2)  

(drop n coll) coll의 처음 n개의 요소를 제외한 나머지로 된 lazy-seq를 리턴한다.

(drop 3 '(1 2 3 4 5 6)) ;=> (4 5 6)
(drop 3 [1 2 3 4 5 6]) ;=> (4 5 6)
(drop 3 [1 2]) ;=> ()  

take-while/drop-while

(take-while pred coll)

(pred el)이 참인 동안의 요소들로 된 lazy-seq를 리턴한다. 즉 처음 (pred el)이 거짓일 때까지의 요소로 된 lazy-seq.

(take-while neg? [-2 -1 0 1 2 3]) ;=> (-2 -1)
(take-while neg? [-2 -1 0 -1 -2 3]) ;=> (-2 -1)

(drop-while pred coll)

(pred el)이 참인 동안의 요소들을 제외한 나머지 요소로 된 lazy-seq를 리턴한다. 즉 처음 (pred el)이 거짓일 때까지의 요소 제외한 lazy-seq.

(drop-while neg? [-1 -2 -6 -7 1 2 3 4 -5 -6 0 1]) ;=> (1 2 3 4 -5 -6 0 1)

for

(for [n (range 5)]
 n)
;=> (0 1 2 3 4)
 
(for [n (range 5)]
 (+ n n)
;=> (0 2 4 6 8)
 
(for [x ['a 'b 'c] 
      y [1 2 3]]
  [x y])
;=> ([a 1] [a 2] [a 3] [b 1] [b 2] [b 3] [c 1] [c 2] [c 3])

clojure에서 for는 제어구조가 아니라 매크로다. 그리고 리스트를 리턴한다. 이것이 다른 언어와 다른 점이다.

for는 매우 막강한 기능을 갖는다. clojuredoc.org의 for 예제 샘플은 for의 막강한 기능을 잘 보여준다.

(참고: http://programming-puzzler.blogspot.kr/2013/03/logic-programming-is-overrated.html)

다음은 그중 일부이다.

(for [x [0 1 2 3 4 5]
      :let [y (* x 3)]
      :when (even? y)]
  y)
;=> (0 6 12)
 
(for [x (range 1 6) 
      :let [y (* x x) 
            z (* x x x)]] 
   [x y z])          
;=> ([1 1 1] [2 4 8] [3 9 27] [4 16 64] [5 25 125])

map

(map f coll)

map 함수는 다음과 같이 수학에서 한 집합에서 다른 집합으로 바꾸는 작업을 한다. f는 coll의 각 요소에 적용된다.

   X                 Y   
 +----+   (f x1)   +----+
 | x1 |  ------->  | y1 |
 |    |   (f x2)   |    |
 | x2 |  ------->  | y2 |
 |    |   (f x3)   |    |
 | x3 |  ------->  | y3 |
 |    |   (f x3)   |    |
 | x4 |  ------->  | y4 |
 +----+            +----+
 

f가 inc이고 coll [1 2 3 4]인 경우 coll의 모든 요소에 inc가 적용된 lazy-seq가 리턴된다.

(map inc [1 2 3 4])  ;=> (2 3 4 5)
   X                Y   
 +---+  (inc 1)   +---+
 | 1 | -------->  | 2 |
 |   |  (inc 2)   |   |
 | 2 | -------->  | 3 |
 |   |  (inc 3)   |   |
 | 3 | -------->  | 4 |
 |   |  (inc 4)   |   |
 | 4 | -------->  | 5 |
 +---+            +---+

map 함수는 clojure programming에서 가장 많이 사용하는 함수이고 기능도 아주 다양하다. clojuredocs.org에는 map함수를 활용하는 다양한 예제 샘플들이 아주 많다.

filter/remove

(filter pred coll)

filter 함수는 coll의 요소 el에 대해 (pred el)이 참인 el들로 된 lazy-seq를 리턴한다.

   X                              Y    
 +----+    (pred x1) = true     +----+
 | x1 | ----------------------> | x1 |
 |    |    (pred x2) = false    |    |
 | x2 | ----------------------> |    |
 |    |    (pred x3) = true     |    |
 | x3 | ----------------------> | x2 |
 |    |    (pred x4) = false    |    |
 | x4 | ----------------------> |    |
 +----+                         +----+
 
(filter even? (range 10)) ;=> (0 2 4 6 8)

remove는 반대이다. 즉 (pred el)이 참이면 제거한다.

(remove even? (range 10)) ;=> (1 3 5 7 9)

keep

(keep f coll)

keep 함수는 coll의 요소 el에 대해 (f el)이 nil이 아닌 리턴값으로 된 lazy-seq를 리턴한다.

   X                         Y    
 +----+    (f x1) = y1     +----+
 | x1 | -----------------> | y1 |
 |    |    (f x2) = nil    |    |
 | x2 | -----------------> |    |
 |    |    (f x3) = y3     |    |
 | x3 | -----------------> | y3 |
 |    |    (f x4) = nil    |    |
 | x4 | -----------------> |    |
 +----+                    +----+
(keep #(if (odd? %) %) (range 10)) ;=> (1 3 5 7 9)

reduce

(reduce f initial coll)

reduce 함수는 coll의 요소 el에 대해 2개의 인수를 받는 함수 (f acc el)의 결과를 다음 el들의 f의 acc에 적용하여 누적한 최종값(acc)을 리턴한다.

 
 (f initial x0) ;=> acc1
     |---------------|
     v
 (f acc1 x1)    ;=> acc2
     |---------------|
     v   
 (f acc2 x2)    ;=> acc3
     |---------------|
     v
 (f accn xn)    ;=> acc (최종값)
 
 

initial이 주어지지 않으면 최초 f 호출시 첫요소 x0가 acc로, x1은 el로 해서 호출된다.

(reduce + [1 2 3 4 5]) ;=> 15

위 reduce가 수행되는 과정은 다음과 같다.

initial이 주어지지 않아서 처음 acc는 1이고, 2는 두번째 파라미터가 된다.

 (+ 1 2)   ;=> 3
    |----------|
    v
 (+ 3 3)   ;=> 6
    |----------|
    v   
 (+ 6 4)   ;=> 10
    |-----------|
    v
 (f 10 5)  ;=> 15 (최종값)

reduce는 매우 강력한 함수이다. 이것만 잘 사용하면 왠만한 제어구조는 거의 소화할 수 있다.

(reduce에서 reduce를 사용하는 매우 복잡한 일도 가능하게 되는데, 가급적 사용하지 않는 것이 코드의 가독성을 키우는데 좋다.)

lecture/4clojure/level-elementary.txt · Last modified: 2019/02/04 14:26 (external edit)