如何在 Clojure 中更新 Ingoring 第一级

how to update-in ingoring the first level in clojure

使用更新输入时,我们需要提供元素的完整路径。但是,如果我想更新二级键为 :MaxInclude

的所有元素怎么办?

例如输入是

(def a  {:store {:type "varchar"},
 :amount {:nullable true, :length nil, :type "float", :MaxInclude "100.02"},
:unit {:type "int"},
:unit-uniform {:type "int" :MaxInclude "100"}
})

所需的输出是(根据类型将 MaxInclude 从字符串转换为 float/int):

{:store {:type "varchar"},
 :amount {:nullable true, :length nil, :type "float", :MaxInclude 100.02},
:unit {:type "int"},
:unit-uniform {:type "int" :MaxInclude 100}
}
(into {}
      (map (fn [[k v]]
             {k (if (contains? v :MaxInclude)
                  (update-in v [:MaxInclude] read-string)
                  v)})
           a))

在这里,我映射键值对并将每个键值对解构为 k 和 v。然后,如果值包含 :MaxInclude,我将对值使用 update-in。最后,我将列表中的对 into 哈希映射。

备注:

  • 如果任何主地图的值不是索引集合,这将在 contains? 上出错。
  • 我使用 read-string 作为将字符串转换为数字的便捷方式,就像 Clojure reader 在编译作为数字文字的字符串时所做的一样。这种方法可能有缺点。

我在想,如果有一个像 update-in 这样的函数可以匹配键谓词函数而不是精确的键值,那就太好了。这是我想出的:

(defn update-all
  "Like update-in except the second parameter is a vector of predicate
  functions taking keys as arguments. Updates all values contained at a
  matching path. Looks for keys in maps only."
  [m [key-pred & key-preds] update-fn]
  (if (map? m)
    (let [matching-keys (filter key-pred (keys m))
          f (fn [acc k]
              (update-in acc [k] (if key-preds
                                   #(update-all %
                                                key-preds
                                                update-fn)
                                   update-fn)))]
      (reduce f m matching-keys))
    m))

有了这个,您需要做的就是:

(update-all a [= #{:MaxInclude}] read-string)

= 用作第一个键匹配函数,因为它在传递一个参数时总是 returns true。第二个是利用集合是函数这一事实。此函数使用非优化递归,但调用堆栈的深度只会与匹配映射级别的数量一样深。