How to select keys in nested maps in Clojure?
假设我有一个这样的地图(
1 | (def m {:a 1 :b 2 :c {:d 3 :e 4} :e { ... } ....}) |
我想创建一个仅包含
1 | {:a 1 :b 2 :d 3} |
我知道我可以使用
1 | (select-keys m [:a :b]) |
但是获得
1 | (select-keys* m [:a :b [:c :d]]) |
Clojure中是否存在这样的功能,或者推荐的方法是什么?
在纯Clojure中,我会这样做:
1 2 3 4 5 6 | (defn select-keys* [m paths] (into {} (map (fn [p] [(last p) (get-in m p)])) paths)) (select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3} |
我更喜欢保持路径类型的规则性,以便为所有路径提供一系列键。 在
1 2 3 4 5 6 | (s/def ::nested-map (s/map-of keyword? (s/or :num number? :map ::nested-map))) (s/def ::path (s/coll-of keyword?)) (s/fdef select-keys* :args (s/cat :m ::nested-map :paths (s/coll-of ::path))) |
或者,您可以对函数使用析构,例如:
1 2 3 4 5 6 7 | (def m {:a 1 :b 2 :c {:d 3 :e 4}}) (defn get-m [{a :a b :b {d :d} :c}] {:a 1 :b b :d d}) (get-m m) ; => {:a 1, :b 2, :d 3} |
您可以使用clojure.walk。
1 2 3 4 5 6 7 8 9 10 11 | (require '[clojure.walk :as w]) (defn nested-select-keys [map keyseq] (w/postwalk (fn [x] (if (map? x) (select-keys x keyseq) (identity x))) map)) (nested-select-keys {:a 1 :b {:c 2 :d 3}} [:a :b :c]) ; => {:a 1, :b {:c 2}} |
我不知道这样的功能是Clojure的一部分。 您可能必须自己编写。 我想出了这个:
1 2 3 4 5 6 7 8 9 10 11 | (defn select-keys* [m v] (reduce (fn [aggregate next] (let [key-value (if (vector? next) [(last next) (get-in m next)] [next (get m next)])] (apply assoc aggregate key-value))) {} v)) |
要求路径是矢量,所以您可以使用
1 2 3 4 | (defn select-keys* [m paths] (reduce (fn [r p] (assoc r (peek p) (get-in m p))) {} paths)) (select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3} |
当然,这假设您的所有终端键都是唯一的。