在不知道密钥的情况下更改嵌套映射值

时间:2015-12-11 17:49:22

标签: clojure

我需要更改嵌套地图中的值,我不知道密钥的值。我想出了以下内容。

;; input  {String {String [String]}}
;; output {String {String String}}

(defn join-z
  [x-to-y-to-z]
  (zipmap (keys x-to-y-to-z)
          (map (fn [y-to-z] (into {} (map (fn [[y z]] {y (clojure.string/join z)})
                                          (seq y-to-z))))
               (seq (vals x-to-y-to-z)))))

(def example
  {"a" {"b" ["c" "d" "e"]}
   "m" {"n" ["o" "p"]}})

;; (join-z example) => {"m" {"n" "op"}, "a" {"b" "cde"}}

这似乎是一个黑客攻击。什么是惯用的clojure来做到这一点?或者,有什么像Haskell的镜头库吗?

更新:基于user5187212回答

(defn update-vals [f m0]
  (reduce-kv (fn [m k v] (assoc m k (f v)))
             {}
             m0))

;; (update-vals clojure.string/join {"b" ["c" "d" "e"]}) => {"b" "cde"}

(defn join-z [x-to-y-to-z]
  (update-vals (partial update-vals clojure.string/join) x-to-y-to-z))

;; (join-z example) => {"m" {"n" "op"}, "a" {"b" "cde"}}

这看起来更优雅。谢谢!

2 个答案:

答案 0 :(得分:2)

简短的回答是肯定的,就是你这样做的方式:)

我会选择更像这样的东西:

(into {} (for [[k v] example]
           [k (into {} (for [[k2 v2] v]
                         [k2 (string/join v2)]))]))

这几乎是一回事。

有一个名为Spectre的库 https://github.com/nathanmarz/specter 查询和转换:

(ns specter.core
  (:require
   [clojure.string :as string]
   [com.rpl.specter :as s]))

(def example
  {"a" {"b" ["c" "d" "e"]}
   "m" {"n" ["o" "p"]}})

(s/transform
 [s/ALL s/LAST s/ALL s/LAST]
 string/join
 example)

我认为这是表达它的一种非常巧妙的方式。

答案 1 :(得分:2)

我建议reduce-kv

对于最后一层,您可以使用以下内容:

(defn foo [x]
  (reduce-kv
    (fn [m k v]
      (assoc m k (clojure.string/join v)))
    {}
    x))

然后根据需要多次调用它......

(reduce-kv 
   (fn [m k v]
     (assoc m k (foo v)))
   {} 
   example)

另一种方法可能超过all nested keys然后

(reduce 
  (fn [m ks]
    (update-in m ks clojure.string/join))
  example
  all-nested-keys)