DanLebrero
DanLebrero

Reputation: 8593

Idiomatic way of checking if a key on a map has a value

What is the idiomatic way of checking if a key on a map has a value? For example if we have:

=> (def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])

To find out all the maps with :foo == 0, I like:

=> (filter (comp zero? :foo) seq-of-maps)
({:foo 0, :bar "baz"})

But if I want to find all the maps with :bar == "hi", the best that I can think of is:

=> (filter #(= (:bar %) "hi") seq-of-maps)
({:foo 1, :bar "hi"})

which I don't find very readable. Is there a better/more idiomatic way of doing it?

Upvotes: 14

Views: 6065

Answers (7)

mikera
mikera

Reputation: 106351

I personally like refactoring this kind of thing to use a clearly named higher order function:

(def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])

(defn has-value [key value]
  "Returns a predicate that tests whether a map contains a specific value"
  (fn [m]
    (= value (m key))))

(filter (has-value :bar "hi") seq-of-maps)
=> ({:foo 1, :bar "hi"})

Downside is that it gives you an extra function definition to manage and maintain, but I think the elegance / code readability is worth it. This approach can also be very efficient from a performance perspective if you re-use the predicate many times.

Upvotes: 5

Dimagog
Dimagog

Reputation: 1925

Simply replace zero? with (partial = "hi") like this:

=> (filter (comp (partial = "hi") :bar) seq-of-maps)

Upvotes: 0

Ankur
Ankur

Reputation: 33637

Your code looks fine to me. Other possible solution can be to use for macro as shown below

(for [m seq-of-maps 
      :let [v (:bar m)] 
      :when (= v "hi")] 
   m)

Upvotes: 0

tnoda
tnoda

Reputation: 1524

user> (def seq-of-maps [{:foo 1 :bar "hi"} {:foo 0 :bar "baz"}])
#'user/seq-of-maps
user> (filter #(-> % :bar (= "hi")) seq-of-maps)
({:foo 1, :bar "hi"})

As Pepijn says, I think idiomatic ways vary according to personal opinions. I sometimes use the -> macro to extract nested parentheses.

Upvotes: 2

ChrisR
ChrisR

Reputation: 1237

clojure.set/index could also be used here

((index seq-of-maps [:foo]) {:foo 0})
((index seq-of-maps [:bar]) {:bar "hi"})

If you want you can wrap it in a function

(defn select-maps [xrel m]
   ((index xrel (keys m)) m))

then

(select-maps seq-of-maps {:foo 0})
(select-maps seq-of-maps {:bar "hi"})

both work - you can also request maps with multiple key/values using index ie:

(select-maps seq-of-maps {:foo 0 :bar "baz"}) 

selects all maps containing foo 0 and bar "baz"

Upvotes: 3

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

Your third example of passing an anonymous function to filter seems like one of the more idomatic methods for finding maps with a given value. I found it quite easy to read.

Upvotes: 2

Pepijn
Pepijn

Reputation: 4253

Idiomatic is subjective, but I'd do

=> (filter (comp #{"hi"} :bar) seq-of-maps)

or what you did.

Upvotes: 15

Related Questions