Reputation: 8593
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
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
Reputation: 1925
Simply replace zero?
with (partial = "hi")
like this:
=> (filter (comp (partial = "hi") :bar) seq-of-maps)
Upvotes: 0
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
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
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
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
Reputation: 4253
Idiomatic is subjective, but I'd do
=> (filter (comp #{"hi"} :bar) seq-of-maps)
or what you did.
Upvotes: 15