簡體   English   中英

Clojure:如果鍵存在,則慣用更新地圖的值

[英]Clojure: idiomatic update a map's value IF the key exists

這是我的問題:我想要一個函數helpme ,它接受一個映射並在且僅當這些鍵存在時用空向量替換鍵:r:g 例如:


輸入:

(helpme {:a "1" :r ["1" "2" "3"] :g ["4" "5"]})

輸出:

{:a "1" :r [] :g []}

輸入:

(helpme {:a "1" :r ["1" "2" "3"]})

輸出:

{:a "1" :r []}

我可以定義一個函數“helpme”來執行此操作,但它過於復雜,我覺得必須有一種更簡單(更慣用)的方法......

這是我完成的過於復雜的方法,如下要求:

(defn c [new-doc k] (if (contains? new-doc k) (assoc new-doc k []) new-doc))
(defn helpme [new-doc] (c (c new-doc :r) :g))
(defn helpme [m]
  (into m (for [[k _] (select-keys m [:r :g])]
            [k []])))

簡而言之,當設置為[]的項目數發生變化時,只需在一處編輯。

在我搜索僅在密鑰確實存在的情況下才更新地圖的 update-in 版本時,Google 堅持我可以在這里找到答案。 對於其他尋求相同事物的人,我創建了以下輔助函數:

(defn contains-in?
  [m ks]
  (not= ::absent (get-in m ks ::absent)))

(defn update-if-contains
  [m ks f & args]
  (if (contains-in? m ks)
    (apply (partial update-in m ks f) args)
    m))

這樣你就可以:

> (def my-data {:a {:aa "aaa"}})

> (update-if-contains my-data [:a :aa] clojure.string/upper-case)
{:a {:aa "AAA"}}

> (update-if-contains my-data [:a :aa] clojure.string/split #" ")
{:a {:aa ["a" "aa"]}}

> (update-if-contains my-data [:a :b] clojure.string/upper-case)
{:a {:aa "aaa"}} ; no change because [:a :b] didn't exist in the map

為此目的使用cond->怎么樣?

(defn helpme [m]
  (cond-> m
    (:r m) (assoc :r [])
    (:g m) (assoc :g [])))
(defn helpme
  [mp]
  (as-> mp m
        (or (and (contains? m :r) (assoc m :r []))
            m)
        (or (and (contains? m :g) (assoc m :g []))
            m)
        m))

如果有第三個替換,我會使用這個函數:

(defn replace-contained [m k v] (or (and (contains? m k) (assoc m k v)) m))

as->是 clojure 1.5 中的新功能,但如果您堅持使用較舊的 clojure 版本,則定義非常簡單:

(defmacro as->
  "Binds name to expr, evaluates the first form in the lexical context
  of that binding, then binds name to that result, repeating for each
  successive form, returning the result of the last form."
  {:added "1.5"}
  [expr name & forms]
  `(let [~name ~expr
         ~@(interleave (repeat name) forms)]
     ~name))

一種選擇:

(defn helpme [m]
  (merge m
         (apply hash-map (interleave
                          (clojure.set/intersection
                           (set (keys m)) #{:r :g})
                          (repeat [])))))

如果這真的像有條件地設置兩個固定鍵的值一樣簡單,我會直接寫出來以保持簡單。

(defn clean [m]
  (let [m (if (:r m) (assoc m :r []) m)
        m (if (:g m) (assoc m :g []) m)]
    m))

如果你想要更通用和可重用的東西,這里有一個選項:

(defn cond-assoc [m & kvs]
  (reduce
    (fn [acc [k v]]
      (if (get acc k)
        (assoc acc k v)
        acc))
    m
    (partition 2 kvs)))

(cond-assoc {:a "1" :r ["1" "2" "3"] :g ["4" "5"]}
            :r []
            :g [])  ; {:r [] :a "1" :g []}

(cond-assoc {:a "1" :r ["1" "2" "3"]}
            :r []
            :g [])  ; {:r [] :a "1"}

通過在比較函數中測試預期的鍵

(sort-by #(if (number? (:priority %))
             (:priority %)
             (java.lang.Integer/MAX_VALUE))
          <
          [{:priority 100} {:priority 10} {:test 1}])




>({:priority 10} {:priority 100} {:test 1})

我真的很喜歡 helpme API 包含要重置的鍵和要重置的默認值:

(defn helpme [m & {:keys [ks v]
                   :or   {ks #{:r :g}
                          v  []}}]
              (apply assoc m (-> m
                                 ;; select only existing keys by selecting from original map
                                 (select-keys ks)
                                 keys
                                 ;; generate defaults for each (handled by applying `assoc`)
                                 (interleave (repeat v)))))

這通過產生它的參數來使用assoc的可變參數形式。

如果你放棄通用 API,它可以短至:

(defn helpme [m]
  (apply assoc m (-> m
                     (select-keys #{:r :g})
                     keys
                     (interleave (repeat [])))))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM