简体   繁体   中英

Is there an idiomatic way to find matching key and value in map in Clojure?

I'm trying to find matching key and value pairs from a map. I'm using the following code:

(defn matches? [m k v]
  (let [val (k m)]
    (= val v)))

my-demo.core=> (matches? {:a 1 :b 2} :b 2)
true
my-demo.core=> (matches? {:a 1 :b 2} :b 3)
false

Another approach using superset? :

my-demo.core=> (superset? #{:a 1 :b 3} #{:a 1})
true
my-demo.core=> (superset? #{:a 1 :b 3} #{:a 2})
false

I have a feeling there is a better way to do this.

My question is: Is there an idiomatic way to find matching key and value in map in Clojure?

This is probably a small enough problem that you could just use this instead of defining a function:

(= ({:a 1 :b 2} :a)
   1)

=> true

I would say this is an idiomatic way, which will work fine for most use cases.


However, it depends on the behaviour you require when testing for a nil value. because the above method would return true for :c nil :

(= ({:a 1 :b 2} :c)
   nil)

=> true

And your function behaves the same way:

(matches? {:a 1 :b 2} :c nil)

=> true

To get around this you could use get with a "not found" value:

(= (get {:a 1 :b 2} :c ::not-found)
   nil)

=> false

This works fine but it's perhaps not as neat. You'd just have to make sure that your "not found" value was never the same as your test value.


If you wanted to really know that a map contains a key with a possibly nil value you would instead have to check both things. Here's a function that would do this while only doing the hash-map lookup once. It uses (find map key) which returns the map entry (the key-value pair) for key, or nil if the key is not present.

(defn contains-kv? [m k v]
  (if-let [kv (find m k)]
    (= (val kv) v)
    false))

(contains-kv? {:a 1 :b nil} :a 1)
=> true

(contains-kv? {:a 1 :b nil} :b nil)
=> true

(contains-kv? {:a 1 :b nil} :c nil)
=> false

Note: I don't think superset? is doing what you think it does. In that example you're using sets, not hash maps, which are completely different:

(clojure.set/superset? #{:a 1 :b 2} #{:a :b})

=> true

Your matches? function looks good to me, though I'd probably remove the let in this case, as it removes a bit of clutter. I'd also rename it to something more precise, though this is the best I can come up with just now:

(defn contains-kv?
  "Returns true if the key k is present in the given map m and it's value matches v."
  [m k v]
  (= (m k) v))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM