4clojure #69 Merge with a Function

4clojure #69 Merge with a Function

関数fといくつかのマップを引数に取る関数を作る。 その関数は、一番目のマップにconjしたマップの残りから構成されたマップを返す。 複数のマップにひとつのキーがあるとき、後ろからのマッピング(f 結果の値 後の値)を 読んだ結果にマッピングされる。

merge-withは使用禁止。

(= (__ * {:a 2, :b 3, :c 4} {:a 2} {:b 2} {:c 5})
   {:a 4, :b 6, :c 20})
(= (__ - {1 10, 2 20} {1 3, 2 10, 3 15})
   {1 7, 2 10, 3 15})
(= (__ concat {:a [3], :b [6]} {:a [4 5], :c [8 9]} {:b [7]})
   {:a [3 4 5], :b [6 7], :c [8 9]})

mapsを2つに分割します。

 ((fn my-merge [func & maps]
(reduce #(println %1 %2) (first maps) (rest maps)))
 * {:a 2, :b 3, :c 4} {:a 2} {:b 2} {:c 5})
; {:a 2, :c 4, :b 3} {:a 2}
; nil {:b 2}
; nil {:c 5}
; nil

rest mapreduceを作用させて欲しいmapを作ります。

((fn my-merge [func & maps]
(reduce 
 (fn [a b];(println a 
  (reduce (fn [x y]
            (println (key y)
                     (a (key y))
                     (val y)
                     (if (nil? (a (key y)))
                       (val y) 
                       (func (a (key y)) (val y))))) '{} b))
 (first maps) (rest maps)))
    - {1 10, 2 20} {1 3, 2 10, 3 15})
;   * {:a 2, :b 3, :c 4} {:a 2} {:b 2} {:c 5})
; 1 10 3 7
; 2 20 10 10
; 3 nil 15 15
; nil

rest map複数になることがあるので、2段目のreduceの引数はa bではなく b aが正解。

かなり手こずりましたが何とか解決しました。

((fn my-merge [func & maps]
    (reduce
     (fn [a b]
       ;(println a b)
       (reduce 
         (fn [x [k v]]
           ;(println x k v)
           (assoc x  k  (if (nil? (b k))
                          v
                          (func v (b k))))
         )
       b a))
     (first maps) (rest maps)))
 * {:a 2, :b 3, :c 4} {:a 2} {:b 2} {:c 5})
;  - {1 10, 2 20} {1 3, 2 10, 3 15})

参考

;; hyone's solution to Merge with a Function
;; https://4clojure.com/problem/69
 
(fn my-merge-with [f & maps]
  (reduce
    (fn [a b]
      (reduce
        (fn [x [k v]]
          (assoc x k (if (b k) (f v (b k)) v)))
        b a))
    (first maps) (rest maps)))
参考 merge-withの定義
(defn merge-with
  "Returns a map that consists of the rest of the maps conj-ed onto
  the first.  If a key occurs in more than one map, the mapping(s)
  from the latter (left-to-right) will be combined with the mapping in
  the result by calling (f val-in-result val-in-latter)."
  {:added "1.0"
   :static true}
  [f & maps]
  (when (some identity maps)
    (let [merge-entry (fn [m e]
            (let [k (key e) v (val e)]
              (if (contains? m k)
                (assoc m k (f (get m k) v))
                (assoc m k v))))
          merge2 (fn [m1 m2]
           (reduce1 merge-entry (or m1 {}) (seq m2)))]
      (reduce1 merge2 maps))))

;; reduce is defined again later after InternalReduce loads
(defn ^:private ^:static
  reduce1
       ([f coll]
             (let [s (seq coll)]
               (if s
         (reduce1 f (first s) (next s))
                 (f))))
       ([f val coll]
          (let [s (seq coll)]
            (if s
              (if (chunked-seq? s)
                (recur f 
                       (.reduce (chunk-first s) f val)
                       (chunk-next s))
                (recur f (f val (first s)) (next s)))
         val))))
参考 mergeの定義
(defn merge
  "Returns a map that consists of the rest of the maps conj-ed onto
  the first.  If a key occurs in more than one map, the mapping from
  the latter (left-to-right) will be the mapping in the result."
  {:added "1.0"
   :static true}
  [& maps]
  (when (some identity maps)
    (reduce1 #(conj (or %1 {}) %2) maps)))

4clojure #67 Prime Numbers

4clojure #67 Prime Numbers

最初のx個の素数を返す関数を作る。

(= (__ 2) [2 3])
(= (__ 5) [2 3 5 7 11])
(= (last (__ 100)) 541)

はじめは、reduceのClojureDocをみて、素数列をつくろうとしましたが、 うまくいかなかったので、prime?を作ってみました。

1 と自分自身以外に正の約数を持たない自然数で、1 でない数のことが素数 なので、その条件をコード化しました。take(dec n)としているのは2から 始まるiterateのためです。

あと、prime?にわたすiterateのせいで、Execution Timed Out! がでてしまうので、求める数の二乗の個数までに制限を入れました。

((fn my-prime [num]
    (letfn [(prime? [n]
               (if (>= 1 (count (filter #(= 0 (mod n %)) 
                     (take (dec n) (iterate inc 2)))))
                 true
                 false))]
    (take num (filter prime? (take (* num num) (iterate inc 2))))))
5)

prime?ができたのであらためてreduce方式のとき方を見直したらすんなりできました。

((fn my-prime [num]
  (take num
    (reduce 
        (fn inner-prime [p n]
          (if (= 0 (count (filter #(= 0 (mod n %)) p)))
            (conj p n)
            p))
      [2]
      (take (* num num) (iterate inc 3)))))
 5)
参考 reduceのClojureDocより
(reduce
  (fn [primes number]
    (if (some zero? (map (partial mod number) primes))
      primes
      (conj primes number)))
  [2]
  (take 1000 (iterate inc 3)))

4clojure #66 Greatest Common Divisor

4clojure #66 Greatest Common Divisor

最大公約数を求める関数を作る。

(= (__ 2 4) 2)
(= (__ 10 5) 5)
(= (__ 5 7) 1)
(= (__ 1023 858) 33)

abの最大公約数とは、aでもbでも割り切れる数のうちの最大値だから

((fn my-gcd [a b]
    (let [m (max a b)]
        (last 
        (filter #(and (= 0 (mod a %)) (= 0 (mod b %)))
            (take m (iterate inc 1))))))
 1023 858)

4clojure #65 Black Box Testing

4clojure #65 Black Box Testing

シーケンスの種類(:map :set :list :vector)を答える。 ポイントはそれらを調べ、動作を理解することです。

ただし、以下は使用禁止。

class
type
Class
vector?
sequential?
list?
seq?
map?
set?
instance?
getClass
(= :map (__ {:a 1, :b 2}))
(= :list (__ (range (rand-int 20))))
(= :vector (__ [1 2 3 4 5 6]))
(= :set (__ #{10 (rand-int 5)}))
(= [:map :set :vector :list] (map __ [{} #{} [] ()]))

動作を調べるといってもよくわからず、ググって答えを探しました。

(fn seq-type [coll]
  (let [base (empty coll)]
    (cond
      (= base {})        :map
      (= base #{})       :set
      (reversible? base) :vector
      (= base ())        :list)))

4clojure #63 Group a Sequence

4clojure #63 Group a Sequence

関数fとシーケンスsを与えてマップを返す関数を作る。 キーはsの各項にfをapplyした値です。 各キーに対応する値はsの順番で連続する項の続くベクターです。

※ group-byは使用禁止。

(= (__ #(> % 5) [1 3 6 8]) {false [1 3], true [6 8]})
(= (__ #(apply / %) [[1 2] [2 4] [4 6] [3 6]])
   {1/2 [[1 2] [2 4] [3 6]], 2/3 [[4 6]]})
(= (__ count [[1] [1 2] [3] [1 2 3] [2 3]])
   {1 [[1] [3]], 2 [[1 2] [2 3]], 3 [[1 2 3]]})

まず、mapで各seqfを作用させて、結果と元の値とのリストを作り ソートして結果ごとに小グループを作る。

((fn my-gr [f coll]
   (partition-by first
       (sort-by first 
          (map #(list (f %) %) coll))))
 #(> % 5) [1 3 6 8])
 ;=> (((false 1) (false 3)) ((true 6) (true 8)))

あとは、求める形式に表現を変更するだけ。

(into {}
  (map #(hash-map (first (first %))
                    (into [] (map second %)))
    '(((false 1) (false 3)) ((true 6) (true 8)))))
 ; => {false [1 3], true [6 8]}

group-byの定義には劣るけれども自力でとけたのでよかったです。

((fn my-gr [f coll]
    (into {}
  (map #(hash-map (first (first %))
                    (into [] (map second %)))
            (partition-by first
              (sort-by first 
                (map #(list (f %) %) coll))))))
 #(> % 5) [1 3 6 8])

参考 group-byの定義

(defn group-by 
  "Returns a map of the elements of coll keyed by the result of
  f on each element. The value at each key will be a vector of the
  corresponding elements, in the order they appeared in coll."
  {:added "1.2"
   :static true}
  [f coll]  
  (persistent!
   (reduce
    (fn [ret x]
      (let [k (f x)]
        (assoc! ret k (conj (get ret k []) x))))
    (transient {}) coll)))