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)))