생성일: 2019년 11월 28일
수정일: 2023년 07월 27일

4clojure - Count Occurrences (55)

  1. 문제
  2. 풀이
    1. 풀이과정

문제

(= (__ [1 1 2 3 2 1 1]) {1 4, 2 2, 3 1})

(= (__ [:b :a :b :a :b]) {:a 2, :b 3})

(= (__ '([1 2] [1 3] [1 3])) {[1 2] 1, [1 3] 2})

제한 : frequencies

풀이

(fn [x] 
  (->>
   (partition-by identity (sort x))
   (map #(list (first %) (count %)))
   (apply concat)
   (apply hash-map)))

풀이과정

처음에는 group-by로 하면 될 것 이라고 생각을 했다.

(group-by count [1 1 2 3 2 1 1])
;;Execution error (UnsupportedOperationException) at user/eval296 (REPL:1). 
;;count not supported on this type: Long

근데 group-by는 string 값만 된다는 것을 알게 되었고, 다른 방법을 고민 했다.

갯수를 알아내기 위해 같은 값을 묶는 것이 필요하다고 생각하여 partition-by를 생각 했다.

(partition-by identity [1 1 2 3 2 1 1])
;; ((1 1) (2) (3) (2) (1 1))

근데 얖에서부터 같은 값만 찾다보니 뒤에 같은 값이 있을 경우에는 같이 묶어주지 못하는 현상이 있어서 sort를 적용 했다. key에 대한 sort도 되는지 테스트를 했다.

(sort [:b :a :b :a :b])
;; (:a :a :b :b :b)

다행히 sort가 되어서 적용을 했다.

(partition-by identity (sort [1 1 2 3 2 1 1]))
;; ((1 1 1 1) (2 2) (3))

이제 map으로 count를 적용하면 될거 같다. 슬슬 길어지므로 ->>를 사용 했다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(count %)))
;; (4 2 1)

어떤 항목의 갯수가 같이 표시되지 않으므로 표시되도록 추가 했다.

;; 오류 상황1
(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(% (count %))))
;; Error printing return value (ClassCastException) at user/eval318$fn (NO_SOURCE_FILE:1).
clojure.lang.Cons cannot be cast to clojure.lang.IFn

;; 오류 상황2
(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #([% (count %)]))
;; Error printing return value (ArityException) at clojure.lang.AFn/throwArity (AFn.java:429).
Wrong number of args (0) passed to: clojure.lang.PersistentVector

마음처럼 잘 안된다...함수를 넣어야 하는데 엉뚱한 값을 넣고 있었다..

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list % (count %)))
;; (((1 1 1 1) 4) ((2 2) 2) ((3) 1))

전체 값이 필요한 것은 아니므로 first로 1개만 표시되도록 했다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
;; ((1 4) (2 2) (3 1))

이걸 이제 hash-map으로 바꾸면 끝이다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(into {}))

;; Execution error (ClassCastException) at user/eval342 (REPL:1). java.lang.Long cannot be cast to java.util.Map$Entry

hash-map 형식이 아니라서 안된단다....

그러면 전체 값을 하나로 합친 후에 하면 되지 않을까? 일단 합쳐보기로 했다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(apply concat))
;; (1 4 2 2 3 1)

근데 일단 다른 문항에 잇는 값들이 제대로 합쳐지는지 궁금해서 테스트 해봤다.

(->>
	(partition-by identity (sort '([1 2], [1 3], [1 3])))
	(map #(list (first %) (count %)))
	(apply concat))
;; ([1 2] 1 [1 3] 2)

원하는데로 합쳐지는 것 같다.

이제 hash-map을 적용 해보자

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(apply hash-map concat))

;; Execution error (IllegalArgumentException) at user/eval366 (REPL:1). No value supplied for key: ([1 3] 2)

key가 필요하다고 안된단다...

그럼 이렇게는 어떨까?

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(apply into hash-map concat))
;; Execution error (ArityException) at user/eval372 (REPL:1).
Wrong number of args (4) passed to: clojure.core/into

into 의 파리미터 갯수가 아니라고 하니 이 방법도 아닌것 같다.

하나로 합친 값에 너무 이상한 함수의 나열이라고 생각이 들어 다시 재정돈 하였다 우선 값을 합친 부분에서 다시 시작해보기로 했다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(apply concat))
;; (1 4 2 2 3 1)

hash-map의 사용법을 다시 테스트 해봤다. 위의 값을 가지고 다시 테스트 했다.

;; 오류 상황
(hash-map '(1 4 2 2 3 1))
;; Execution error (IllegalArgumentException) at user/eval418 (REPL:1). No value supplied for key: (1 4 2 2 3 1)

(hash-map 1 4 2 2 3 1)
;; {1 4, 3 1, 2 2}

사용법을 확실하게 알고 있지 않아서 발생한 오류라고 생각 한다...이제 apply로 합치면 문제가 해결 된다.

(->>
	(partition-by identity (sort [1 1 2 3 2 1 1]))
	(map #(list (first %) (count %)))
	(apply concat)
	(apply hash-map))

Solved 2 문제를 풀고 나서 다른 방식도 있다는 것을 알게되어 분석 하고 싶어졌다.

(comp (partial apply zipmap) 
      (juxt keys (comp (partial map count) vals)) 
      (partial group-by identity))

위에 답에 대한 과정을 하나하나 뜯어 봤다.

((partial group-by identity) [1 1 2 3 2 1 1])
;; {1 [1 1 1 1], 2 [2 2], 3 [3]}

(group-by identity [1 1 2 3 2 1 1])
;; {1 [1 1 1 1], 2 [2 2], 3 [3]}

group-by가 string에 대한 것인줄 알았는데 아니라는 것을 알게 되었다... identity가 이럴때도 사용 가능하다는 것을 다시한번 알게 되었다. 근데 왜 partial을 사용한건지는 잘 모르겠다. group-by만 해도 될거 같은데..

((comp (partial apply zipmap)
      (juxt keys (comp (partial map count) vals))
      (group-by identity)) [1 1 2 3 2 1 1])
;; Execution error (ArityException) at user/eval474 (REPL:3).
Wrong number of args (1) passed to: clojure.core/group-by

안된다...partial이 있으면 무언가 준비된 상태로 둔다는 생각이 들었다.

(apply #(conj [0 1] %) [2 3 4 5])
;; Execution error (ArityException) at user/eval482 (REPL:1).
Wrong number of args (4) passed to: user/eval482/fn--483

(apply #(conj [0 1] %&) [2 3 4 5])
;; [0 1 (2 3 4 5)]

(apply (partial conj [0 1]) [2 3 4 5])
;; [0 1 2 3 4 5] 

partial의 예제를 보니 실행을 할때 그때 조합을 만들어서 실행시키는 역할이 partial인 것 같다.

간단한 예제를 comp와 엮어서 만들어 봤을 때 아무래도 comp의 영향으로 partial이 없으면 안되는 것 같다.

((comp (apply +)) [1 2 3 4 5 6])
;; Execution error (ArityException) at user/eval492 (REPL:1).
Wrong number of args (1) passed to: clojure.core/apply

((comp (partial apply +)) [1 2 3 4 5 6])
;; 21

juxt는 원하는 항목을 vector로 엮어주는 함수이다. 즉, (list (first x) (count x))를 juxt로 얶어서 만들었다고 생각하면 될 것 같다.

((comp (juxt keys (comp (partial map count) vals)) 
      (partial group-by identity) [1 1 2 3 2 1 1]) 
;; {1 [1 1 1 1], 2 [2 2], 3 [3]} -> 1번 항목
;; keys : (1 2 3), vals : [[1 1 1 1] [2 2] [3]]
;; [(1 2 3) (4 2 1)] -> 2번 항목

결과로 나오는 (1, 2, 3)은 1번 항목의 hash-map keys로 엮은 값이고, (4 2 1)은 1번 항목의 hash-map vals로 가져온 (comp (partial map count) vals)로 계산된 값의 list로 생각하면 된다. 그래서 2번 항목이 계산 결과 값으로 나온다.

zipmap은 [(1 2 3) (4 2 1)]의 값을 합쳐서 하나의 hash-map으로 만들어준다. apply 없이 예제를 만든다면 아래와 같다.

(zipmap '(1 2 3) '(4 2 1))
;; {1 4, 2 2, 3 1}

apply로 엮으면 아래와 같은 형식이 된다.

(apply zipmap ['(1 2 3) '(4 2 1)])
;; {1 4, 2 2, 3 1}

comp와 엮이므로 partial이 있어야 계산이 완료가 된다.

Solved 3

reduce #(assoc % %2 (+ 1 (% %2 0))) {}

이건 위의 방법보다 좀 더 간단한 문제이다.

우선 안쪽부터 분석을 해보면

({} 1 0)
;; 0

hash-map안에 1이라는 값이 있으면 그 값을 반환하고 없으면 0을 반환한다는 얘기이다. 현재 상황에서는 {}안에 아무런 값이 없으므로 0이 된다.

(+ 1 0)
;; 1
(assoc {} 1 1)
;; {1 1}

그 값이 위와 같은 과정을 거치면 {1 1}이 된다.

다음 반복시에는 아래와 같은 과정을 거친다

({1 1} 1 0)
;; 1
(+ 1 1)
;; 2
(assoc {1 1} 1 2)
;; {1 2}

위 과정은 {1 1} -> {1 2}로 바뀌는 과정을 보여주고 있다, reduce 연산을 마지막까지 거치면 정답은 같아진다.

Solved 4

#(apply merge-with + (map (fn [a] {a 1}) %))

apply에 대한 영향을 주는 함수는 (merge-with +) 에 대해서 영향을 주는 것이지 map에서 부터 영향을 주는 것은 아니다 그러므로 아래와 같은 동작이 되는 것은 아니다

(map (fn [a] {a 1}) 1 1 2 3 2 1 1)
;; Error printing return value (IllegalArgumentException) at clojure.lang.RT/seqFrom (RT.java:557). Don't know how to create ISeq from: java.lang.Long

map 의 연산은 아래와 같은 결과를 준다.

(map (fn [a] {a 1}) [1 1 2 3 2 1 1])
;; ({1 1} {1 1} {2 1} {3 1} {2 1} {1 1} {1 1})

apply의 영향으로 merge-with +의 연산은 아래와 같은 방식으로 된다.

(merge-with + {1 1} {1 1} {2 1} {3 1} {2 1} {1 1} {1 1})
;; {1 4, 2 2, 3 1}

보통 merge는 아래와 같은 동작을 하는데 merge-with는 f를 받아서 추가적인 연산을 하게 된다는 것을 알았다.

(merge {1 1} {1 1} {2 1} {3 1} {2 1} {1 1} {1 1})
;; {1 1, 2 1, 3 1} -> 덮어쓰는 동작을 함

(merge-with + {1 1} {1 1} {2 1} {3 1} {2 1} {1 1} {1 1})
;; {1 4, 2 2, 3 1} -> + 함수의 영향으로 value에 + 를 한다.

merge-with에 익명함수를 만들려면 가변 인자를 받아야 한다.

(merge-with #(list %&) {1 1} {1 1} {2 1} {3 1} {2 1} {1 1} {1 1})
;; {1 ((((((1 1)) 1)) 1)), 2 ((1 1)), 3 1}
Tags: 4clojure Today I Learn