What is the simplest way to find out if a set contains maps with given key values in Clojure?

Viewed 222

I really like using contains? because it's so terse and readable. I want to see if a set contains maps that have the same key and value pairs of an example that also had other key value pairs. I'm pretty sure contains? won't work here. Is there an alternative? Maybe I'll have to write one (I'm finally getting into the mindset!). For example, if I had

(def some-set #{{:foo "bar" :beep "boop"}{:foo "bar"} {:foo "bar" :hi "there"}})

what would be a quick way to know if it had any maps that matched {:foo "bar" :one "two"} on :foo "bar"?

3 Answers

Edited: Remembering that a map is a collection of key-value vectors, here is an implementation for the predicate submap?:

(defn submap?
  "Returns true if subm is a submap of m, false otherwise."
  [subm m]
  (every? (fn [[k v]] (= (get m k ::not-found) v)) subm))

This predicate can be used to filter any collection:

(filter #(submap? {:a 1 :b 2} %) [{:a 1} {:a 1 :b 2 :c 3}])
=> ({:a 1, :b 2, :c 3})

Original answer

This solution works but is slower than my updated answer, due to the construction of (set m) for large m

(defn submap?
  "Returns true if subm is a submap of m, false otherwise."
  [subm m]
  (let [kvs (set m)]
    (every? kvs subm)))

A generic way would be to write a predicate, that checks if a map contains another map. This can be done using select-keys to only get a map with certain keys; using the keys from the map to compare and then just comparing the result will give you that.

(def maps #{{:foo "bar" :beep "boop"} {:foo "bar"} {:foo "bar" :hi "there"} {:foo "baz"}})

(defn submap?
  [submap m]
  (= (select-keys m (keys submap)) submap))

(println
  (filter (partial submap? {:foo "bar"}) maps))
; → ({:foo bar, :beep boop} {:foo bar, :hi there} {:foo bar})

Yet this is just a simple sequential search. This does not (and AFAIR there is nothing in core to help) utilize your maps being in a set. Also note, that the order of the result is undefined since the order of sets is too.

You can find many predicates of this nature and related helper functions in the Tupelo library, in particular:

  • submap?
  • submatch?
  • wild-match?
  • wild-submatch?

These are especially helpful in writing unit tests. For example, you may only care about certain fields like :body when testing a webserver response, and you want to ignore other fields like the IP address or a timestamp.

The unit tests show the code in action.

Related