根据预期的键过滤地图

问题描述 投票:0回答:3

在我的 Clojure Web 应用程序中,我有各种模型命名空间,其函数将映射作为参数,并以某种方式将该映射插入数据库。我希望在插入之前能够仅从地图中取出所需的键。

一个基本的例子是:

(let [msg-keys [:title :body]
      msg {:title "Hello" :body "This is an example" :somekey "asdf" :someotherkey "asdf"}]
  (select-keys msg msg-keys))

;; => {:title "Hello" :body "This is an example"}
当地图有些复杂并且我想选择一组特定的嵌套键时,

select-keys
不是一个选项:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}]
  (some-select-key-fn person [:name [:first] :something [:a :b]]))

;; => {:name {:first "john"} :something {:a "a" :b "b"}}

有没有办法用核心功能来做到这一点?有没有办法纯粹通过解构来做到这一点?

clojure hashmap destructuring
3个回答
3
投票

这个主题已在 Clojure Google Group 中进行了讨论,并提供了一些解决方案。

解构可能是最接近“核心”功能的,如果您的问题相当静态并且映射具有所有预期的键(从而避免

nil

),则可能是一个很好的解决方案。它可能看起来像:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}} {{:keys [first]} :name {:keys [a b]} :something} person] {:name {:first first} :something {:a a :b b}}) ;; => {:name {:first "john"}, :something {:a "a", :b "b"}}

下面是对 Clojure Google Group 线程中解决方案的调查,应用于您的示例地图。他们对于如何指定要选择的嵌套键有不同的看法。

这是

Christophe Grand解决方案

(defprotocol Selector (-select [s m])) (defn select [m selectors-coll] (reduce conj {} (map #(-select % m) selectors-coll))) (extend-protocol Selector clojure.lang.Keyword (-select [k m] (find m k)) clojure.lang.APersistentMap (-select [sm m] (into {} (for [[k s] sm] [k (select (get m k) s)]))))

使用它需要稍微修改一下语法:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] (select person [{:name [:first] :something [:a :b]}])) ;; => {:something {:b "b", :a "a"}, :name {:first "john"}}

这是 Moritz Ulrich 的解决方案(他警告说,它不适用于以 seq 作为键的映射):

(defn select-in [m keyseq] (loop [acc {} [k & ks] (seq keyseq)] (if k (recur (if (sequential? k) (let [[k ks] k] (assoc acc k (select-in (get m k) ks))) (assoc acc k (get m k))) ks) acc)))

使用它需要另一种稍微修改的语法:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] (select-in person [[:name [:first]] [:something [:a :b]]])) ;; => {:something {:b "b", :a "a"}, :name {:first "john"}}

这是 Jay Fields 的解决方案:

(defn select-nested-keys [m top-level-keys & {:as pairs}] (reduce #(update-in %1 (first %2) select-keys (last %2)) (select-keys m top-level-keys) pairs))

它使用不同的语法:

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] (select-nested-keys person [:name :something] [:name] [:first] [:something] [:a :b])) ;; => {:something {:b "b", :a "a"}, :name {:first "john"}}

这是 Baishampayan Ghose 的

解决方案

(defprotocol ^:private IExpandable (^:private expand [this])) (extend-protocol IExpandable clojure.lang.Keyword (expand [k] {k ::all}) clojure.lang.IPersistentVector (expand [v] (if (empty? v) {} (apply merge (map expand v)))) clojure.lang.IPersistentMap (expand [m] (assert (= (count (keys m)) 1) "Number of keys in a selector map can't be more than 1.") (let [[k v] (-> m first ((juxt key val)))] {k (expand v)})) nil (expand [_] {})) (defn ^:private extract* [m selectors expand?] (let [sels (if expand? (expand selectors) selectors)] (reduce-kv (fn [res k v] (if (= v ::all) (assoc res k (m k)) (assoc res k (extract* (m k) v false)))) {} sels))) (defn extract "Like select-keys, but can select nested keys. Examples - (extract [{:b {:c [:d]}} :g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) ;=> {:g 42, :b {:c {:d 1}}} (extract [:g] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) ;=> {:g 42} (extract [{:b [:c]} :xxx] {:a 1 :b {:c {:d 1 :e 2}} :g 42 :xxx 11}) ;=> {:b {:c {:d 1, :e 2}}, :xxx 11} Also see - exclude" [selectors m] (extract* m selectors true))

它使用另一种语法(并且参数相反):

(let [person {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}}] (extract [{:name [:first]} {:something [:a :b]}] person)) ;; => {:name {:first "john"}, :something {:a "a", :b "b"}}
    

0
投票
您最好的选择可能是在结构的每个嵌套部分上使用选择键。

(-> person (select-keys [:name :something]) (update-in [:name] select-keys [:first]) (update-in [:something] select-keys [:a :b]))

您当然可以使用上述的通用版本来实现您在函数中建议的语法(最有可能使用

reduce

 而不是 
->
 形式,并对每个嵌套的键选择进行递归调用)。解构没有多大帮助,它使绑定嵌套数据变得方便,但对于构造值来说并不是那么有用。

这是我如何使用

reduce

 和递归来做到这一点:

(defn simplify [m skel] (if-let [kvs (not-empty (partition 2 skel))] (reduce (fn [m [k nested]] (if nested (update-in m [k] simplify nested) m)) (select-keys m (map first kvs)) kvs) m))

请注意,您提出的参数格式不方便,所以我稍微更改了它

user=> (simplify {:name {:first "john" :john "smith"} :age 40 :weight 155 :something {:a "a" :b "b" :c "c" :d "d"}} [:name [:first nil] :something [:a nil :b nil]]) {:something {:b "b", :a "a"}, :name {:first "john"}}

您提出的语法将需要更复杂的实现


0
投票
我会在没有钥匙的情况下死在这里,然后交出我在过去几年中积累的 2121 点。还要修复你们对人的攻击。使某人失去那里的福祉。会受到联邦法律的惩罚。一些平原地区的替代活动对我来说是多么不利。曾经让我每周工作 40 多个小时,按时支付我的 CS 费用,为卡车提供食物资金和汽油,并全额支付,这让我发疯了,呵呵。唯一的选择是仍然攻击我。相信这一件事,让我告诉你一件事,问题是等一分钟哦,是的,我爱你,我也该做什么。我已经失去了 100 件东西,比如数百万美元,因为住在一辆现在没有钥匙的吉普车里......好吧,我知道并且知道更多谁在乎你们是否拿走我的文件更改我的时钟以获取更新加上一种后端 hi hjack Y xozox 的方法

© www.soinside.com 2019 - 2024. All rights reserved.