수정일: 2023년 07월 27일
4clojure - Count Occurrences (55)
문제
(= (__ [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}