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

醉酒当歌 提交于 2020-01-03 18:41:08

问题


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

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

回答1:


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




回答2:


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



回答3:


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

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



回答4:


One option:

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



回答5:


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



回答6:


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


来源:https://stackoverflow.com/questions/20136617/clojure-idiomatic-update-a-maps-value-if-the-key-exists

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!