Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to select keys in nested maps in Clojure?

Tags:

clojure

Let's say I have a map (m) like this:

(def m {:a 1 :b 2 :c {:d 3 :e 4} :e { ... } ....})

I'd like to create a new map only containing :a, :b and :d from m, i.e. the result should be:

{:a 1 :b 2 :d 3}

I know that I can use select-keys to easily get :a and :b:

(select-keys m [:a :b])

But what's a good way to also get :d? I'm looking for something like this:

(select-keys* m [:a :b [:c :d]])

Does such a function exists in Clojure or what's the recommended approach?

like image 929
Johan Avatar asked Aug 11 '16 10:08

Johan


4 Answers

As an alternative you can use destructing on a function, for example:

(def m {:a 1 :b 2 :c {:d 3 :e 4}})

(defn get-m
  [{a :a b :b {d :d} :c}]
  {:a 1 :b b :d d})

(get-m m) ; => {:a 1, :b 2, :d 3}
like image 94
Frank C. Avatar answered Nov 09 '22 04:11

Frank C.


In pure Clojure I would do it like this:

(defn select-keys* [m paths]
  (into {} (map (fn [p]
                  [(last p) (get-in m p)]))
        paths))

(select-keys* m [[:a] [:b] [:c :d]]) ;;=> {:a 1, :b 2, :d 3}

I prefer keeping the type of a path regular, so a sequence of keys for all paths. In clojure.spec this would read as

(s/def ::nested-map (s/map-of keyword? 
                              (s/or :num number? :map ::nested-map)))
(s/def ::path (s/coll-of keyword?))
(s/fdef select-keys*
        :args (s/cat :m ::nested-map 
                     :paths (s/coll-of ::path)))
like image 45
Michiel Borkent Avatar answered Nov 09 '22 06:11

Michiel Borkent


You can use clojure.walk.

(require '[clojure.walk :as w])

(defn nested-select-keys
  [map keyseq]
  (w/postwalk (fn [x]
                (if (map? x)
                  (select-keys x keyseq)
                  (identity x))) map))

(nested-select-keys {:a 1 :b {:c 2 :d 3}} [:a :b :c])
  ; => {:a 1, :b {:c 2}}
like image 3
J. Matanle Avatar answered Nov 09 '22 04:11

J. Matanle


I'm not aware of such a function being part of Clojure. You'll probably have to write it yourself. I've came up with this :

(defn select-keys* [m v]
  (reduce 
    (fn [aggregate next]
      (let [key-value (if (vector? next)
                        [(last next)
                         (get-in m next)]
                        [next
                         (get m next)])]
        (apply assoc aggregate key-value)))
    {}
    v))
like image 2
Viktor K. Avatar answered Nov 09 '22 05:11

Viktor K.