if my structure is
{ :a :A
:b :B
:c {
:d :D
}
:e {
:f {
:g :G
:h :H
}
}
}
I
(defn keys-in [m]
(if (map? m)
(vec
(mapcat (fn [[k v]]
(let [sub (keys-in v)
nested (map #(into [k] %) (filter (comp not empty?) sub))]
(if (seq nested)
nested
[[k]])))
m))
[]))
;; tests
user=> (keys-in nil)
[]
user=> (keys-in {})
[]
user=> (keys-in {:a 1 :b 2}))
[[:a] [:b]]
user=> (keys-in {:a {:b {:c 1}}})
[[:a :b :c]]
user=> (keys-in {:a {:b {:c 1}} :d {:e {:f 2}}})
[[:a :b :c] [:d :e :f]]
If you don't need a lazy result and just want to be fast, try using reduce-kv
.
(defn keypaths
([m] (keypaths [] m ()))
([prev m result]
(reduce-kv (fn [res k v] (if (map? v)
(keypaths (conj prev k) v res)
(conj res (conj prev k))))
result
m)))
If you also want to support vector indices (as with get-in
or update-in
), test with associative?
instead of map?
. If you want intermediate paths, you can conj those on too. Here's a variant:
(defn kvpaths-all2
([m] (kvpaths-all2 [] m ()))
([prev m result]
(reduce-kv (fn [res k v] (if (associative? v)
(let [kp (conj prev k)]
(kvpaths-all2 kp v (conj res kp)))
(conj res (conj prev k))))
result
m)))
Obligatory zippers version
(require '[clojure.zip :as z])
(defn keys-in [m]
(letfn [(branch? [[path m]] (map? m))
(children [[path m]] (for [[k v] m] [(conj path k) v]))]
(if (empty? m)
[]
(loop [t (z/zipper branch? children nil [[] m]), paths []]
(cond (z/end? t) paths
(z/branch? t) (recur (z/next t), paths)
:leaf (recur (z/next t), (conj paths (first (z/node t)))))))))
Working on something similar for a personal project and this is my naive implementation:
(defn keys-in
[m parent-keys]
(mapcat (fn [[k v]]
(if (map? v)
(keys-in v (conj parent-keys k))
(vector (conj parent-keys k v))))
m))
Use it from the repl:
(keys-in <your-map> [])
Fancy way:
(map (comp vec drop-last) (keys-in <your-map> []))
Here is an implementation which returns all keys (not just the terminal keys) based on lazy-seq:
(defn keys-in
([m] (if (map? m) (keys-in (seq m) [])))
([es c]
(lazy-seq
(when-let [e (first es)]
(let [c* (conj c (key e))]
(cons c* (concat (if (map? (val e)) (keys-in (seq (val e)) c*))
(keys-in (rest es) c))))))))