Find if a map contains multiple keys
Asked Answered
V

4

10

I'm new to Clojure and I was wondering if there was a way to test if a map has multiple keys. I've noticed that contains? only checks for one key

What i'm trying to do:

(def mario 
    {:position {:x 1 :y 2}
     :velocity {:x 2 :y 0}
     :mass 20})

;;Test if mario has a position and a velocity
(contains-many? mario :position :velocity) ;;true

;;Test if mario has a mass and a jump-height
(contains-many? mario :mass :jump-height) ;;false

basically, is there a function like contains-many? in the clojure library and if not how would you implement the contains-many? function?

Vanhook answered 13/1, 2015 at 2:24 Comment(0)
P
12

I don't know any function that does that. It is also necessary to define whether you want the map to contain every key or just some keys. I chose the every case, if you wanted the some version, just replace every? with some?.

My direct, not optimized, version is:

(defn contains-many? [m & ks]
  (every? #(contains? m %) ks))

Which has been tested with:

(deftest a-test
  (testing "Basic test cases"
    (let [m {:a 1 :b 1 :c 2}]
      (is (contains-many? m :a))
      (is (contains-many? m :a :b))
      (is (contains-many? m :a :b :c))
      (is (not (contains-many? m :a :d)))
      (is (not (contains-many? m :a :b :d))))))

Edit: Simplified using noisesmith's suggestion

Piccadilly answered 13/1, 2015 at 2:57 Comment(5)
Cool! Thanks. Yeah, I wanted the function to test if a map had every key. Thanks. Quick question though: What is this syntax: #(contains? m %) Is this like currying or pattern-matching or something?Vanhook
That is syntactic sugar to create an anonymous function of one argument (which is denoted by %). It is equivalent to (fn [k] (contains? m k)). clojuredocs.org/clojure.core/fnPiccadilly
Cool! Thanks. I see. The syntactic sugar does make it a little more compact and readable.Vanhook
simpler version: (every? #(contains? m %) ks)Gentilis
some, not some?Bergquist
C
23

The correct answer was shown, but I would like to point out another elegant solution which assums knowledge about the map values. When you know for sure they are truthy (that is: not nil and not false):

(every? m ks)

This is due to the fact that maps are (unary) functions which return the corresponding value to the argument. But note that ({:x nil} :x) => nil.

Conflagrant answered 13/1, 2015 at 17:52 Comment(1)
Maps are functions?! Wow! Well, I know for sure that my values are not nil but they can be false. So, I don't think I can use this trick this time but I'll certainly keep it in mind for the future, thanks!Vanhook
P
12

I don't know any function that does that. It is also necessary to define whether you want the map to contain every key or just some keys. I chose the every case, if you wanted the some version, just replace every? with some?.

My direct, not optimized, version is:

(defn contains-many? [m & ks]
  (every? #(contains? m %) ks))

Which has been tested with:

(deftest a-test
  (testing "Basic test cases"
    (let [m {:a 1 :b 1 :c 2}]
      (is (contains-many? m :a))
      (is (contains-many? m :a :b))
      (is (contains-many? m :a :b :c))
      (is (not (contains-many? m :a :d)))
      (is (not (contains-many? m :a :b :d))))))

Edit: Simplified using noisesmith's suggestion

Piccadilly answered 13/1, 2015 at 2:57 Comment(5)
Cool! Thanks. Yeah, I wanted the function to test if a map had every key. Thanks. Quick question though: What is this syntax: #(contains? m %) Is this like currying or pattern-matching or something?Vanhook
That is syntactic sugar to create an anonymous function of one argument (which is denoted by %). It is equivalent to (fn [k] (contains? m k)). clojuredocs.org/clojure.core/fnPiccadilly
Cool! Thanks. I see. The syntactic sugar does make it a little more compact and readable.Vanhook
simpler version: (every? #(contains? m %) ks)Gentilis
some, not some?Bergquist
C
3

You could leverage clojure.set/subset?

(clojure.set/subset?
  #{:position :velocity}
  (set (keys mario)))
Calyptra answered 12/3, 2019 at 19:29 Comment(0)
C
2

Here's another solution using clojure.set that works with non-truthy values:

(require '[clojure.set :as set])

(defn contains-many? [m & ks]
  (empty? (set/difference (set ks) (set (keys m)))))
Convent answered 18/7, 2016 at 13:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.