简体   繁体   English

Clojure:如果键存在,则惯用更新地图的值

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

Here's my problem: I want a function helpme that takes a map and replaces the keys :r and :g with empty vectors if and only if those keys exist.这是我的问题:我想要一个函数helpme ,它接受一个映射并在且仅当这些键存在时用空向量替换键:r:g For example:例如:


Input:输入:

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

Output:输出:

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

Input:输入:

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

Output:输出:

{:a "1" :r []}

I can define a function "helpme" that does this, but it's overly complicated, and I feel like there must be an easier (more idiomatic) way...我可以定义一个函数“helpme”来执行此操作,但它过于复杂,我觉得必须有一种更简单(更惯用)的方法......

Here's the overly complicated way I've done, as requested below:这是我完成的过于复杂的方法,如下要求:

(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 []])))

Short, and only requires editing in one place when the number of items to set to [] changes.简而言之,当设置为[]的项目数发生变化时,只需在一处编辑。

In my search for a version of update-in which only updated the map if the key actually existed, Google insisted that I could find my answer here.在我搜索仅在密钥确实存在的情况下才更新地图的 update-in 版本时,Google 坚持我可以在这里找到答案。 For others in search of the same thing I've created the following helper functions:对于其他寻求相同事物的人,我创建了以下辅助函数:

(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))

That way you could:这样你就可以:

> (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

what about using cond-> for that purpose?为此目的使用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))

if there were a third replacement, I would use this function:如果有第三个替换,我会使用这个函数:

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

as-> is new in clojure 1.5 but the definition is very simple if you are stuck using an older clojure version: 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))

One option:一种选择:

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

If this is really as simple as conditionally setting the value of two fixed keys, I'd just write it out long hand to keep it simple.如果这真的像有条件地设置两个固定键的值一样简单,我会直接写出来以保持简单。

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

If you want something more general and reusable, here's an option:如果你想要更通用和可重用的东西,这里有一个选项:

(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"}

By testing for the expected key in the compare function通过在比较函数中测试预期的键

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




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

I really like the helpme API to include the keys to reset and the default value to reset to:我真的很喜欢 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)))))

This uses assoc s variadic form by production the arguments to it.这通过产生它的参数来使用assoc的可变参数形式。

If you give up the general API it can be as short as:如果你放弃通用 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