2
votes

Say I have a map m like {"good1" 1, "bad1" 1, "good2" 2, "bad2" 2}, and I'd like to remove entries based on some predicate over the keys of the map, one way to do this would be:

(defn dissoc-by [f m] (->> m (filter (complement f)) (into {})))
(dissoc-by #(.contains (first %1) "bad") m)
 => {"good1" 1, "good2" 2}

Is there a more idiomatic way of doing this in clojure?

3

3 Answers

6
votes

Given

(def data {"good1" 1, "bad1" 1, "good2" 2, "bad2" 2})

define

(defn remove-keys [pred m]
  (apply dissoc m (filter pred (keys m))))

then

(remove-keys #(string/includes? % "bad") data)
=> {"good1" 1, "good2" 2}

Similar functions are defined in/at The Clojure Cookbook.

4
votes

This is a very normal way of going about it, with the one correction that a sequence from a map is a sequence of pairs [key value] rather than just the keys, so your filter function needs to use the key explicitly

user> (defn dissoc-by [f m] (->> m (filter #(f (first %))) (into {})))
#'user/dissoc-by
user> (dissoc-by #(.contains % "good") m)
{"good1" 1, "good2" 2}

If you would like to get fancy about it and use a transducer this function can be made more efficient by eliminating the intermediate sequences that are allocated to move data from one step to the next.

user> (defn dissoc-by [f m]
        (into {} (filter #(f (first %))) m))
#'user/dissoc-by
user> (dissoc-by #(.contains % "good") m)
{"good1" 1, "good2" 2}

into takes an optional middle argument, a transducer function, that can filter or transform the data as it is pored into the collection.

1
votes

Here is the code that does exactly what you need:

(def data {"good1" 1, "bad1" 1, "good2" 2, "bad2" 2})

(into (empty data) 
      (filter (fn [[k v]] (clojure.string/includes? k "good")) 
              data))

{"good1" 1, "good2" 2}