How can I get the nested keys of a map in clojure?

后端 未结 11 1110
梦如初夏
梦如初夏 2020-12-15 10:59

if my structure is

{ :a :A
  :b :B
  :c {
       :d :D
     }
  :e {
       :f {
            :g :G
            :h :H
          }
     }
}

I

相关标签:
11条回答
  • 2020-12-15 11:23
    (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]]
    
    0 讨论(0)
  • 2020-12-15 11:24

    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)))
    
    0 讨论(0)
  • 2020-12-15 11:26

    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)))))))))
    
    0 讨论(0)
  • 2020-12-15 11:30

    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> []))
    
    0 讨论(0)
  • 2020-12-15 11:35

    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))))))))
    
    0 讨论(0)
提交回复
热议问题