depth first tree traversal accumulation in clojure

拈花ヽ惹草 提交于 2019-12-12 14:05:44

问题


I'd like to take a tree-like structure like this:

{"foo" {"bar" "1" "baz" "2"}}

and recursively traverse while remembering the path from the root in order to produce something like this:

["foo/bar/1", "foo/baz/2"]

Any suggestions on how this can be done without zippers or clojure.walk?


回答1:


As nberger does, we separate enumerating the paths from presenting them as strings.

Enumeration

The function

(defn paths [x]
  (if (map? x)
    (mapcat (fn [[k v]] (map #(cons k %) (paths v))) x)
    [[x]]))

... returns the sequence of path-sequences of a nested map. For example,

(paths {"foo" {"bar" "1", "baz" "2"}})
;(("foo" "bar" "1") ("foo" "baz" "2"))

Presentation

The function

#(clojure.string/join \/ %)

... joins strings together with "/"s. For example,

(#(clojure.string/join \/ %) (list "foo" "bar" "1"))
;"foo/bar/1"

Compose these to get the function you want:

(def traverse (comp (partial map #(clojure.string/join \/ %)) paths))

... or simply

(defn traverse [x]
  (->> x
      paths
      (map #(clojure.string/join \/ %))))

For example,

(traverse  {"foo" {"bar" "1", "baz" "2"}})
;("foo/bar/1" "foo/baz/2")

  • You could entwine these as a single function: clearer and more useful to separate them, I think.
  • The enumeration is not lazy, so it will run out of stack space on deeply enough nested maps.



回答2:


This is my attempt using tree-seq clojure core function.

(def tree {"foo" {"bar" "1" "baz" "2"}})

(defn paths [t]
  (let [cf (fn [[k v]] 
               (if (map? v)
                 (->> v
                      (map (fn [[kv vv]] 
                             [(str k "/" kv) vv]))
                      (into {}))
                 (str k "/" v)))]
  (->> t 
       (tree-seq map? #(map cf %))
       (remove map?)
       vec)))

(paths tree) ; => ["foo/bar/1" "foo/baz/2"]

Map keys are used to accumulate paths.




回答3:


I did something real quick using accumulator, but it isn't depth first.

(defn paths [separator tree]
  (let [finished? (fn [[_ v]] ((complement map?) v))]
    (loop [finished-paths nil
           path-trees (seq tree)]
      (let [new-paths (mapcat
                        (fn [[path children]]
                          (map
                            (fn [[k v]]
                              (vector (str path separator k) v))
                            children))
                        path-trees)
            finished (->> (filter finished? new-paths)
                              (map
                                (fn [[k v]]
                                  (str k separator v)))
                              (concat finished-paths))
            remaining-paths (remove finished? new-paths)]
        (if (seq remaining-paths)
          (recur finished remaining-paths)
          finished)))))

In the repl

(clojure-scratch.core/paths "/" {"foo" {"bar" {"bosh" "1" "bash" "3"} "baz" "2"}})
=> ("foo/baz/2" "foo/bar/bash/3" "foo/bar/bosh/1")



回答4:


The following uses recursive depth first traversal:

(defn combine [k coll]
  (mapv #(str k "/" %) coll))

(defn f-map [m]
  (into []
        (flatten
         (mapv (fn [[k v]]
                 (if (map? v)
                   (combine k (f-map v))
                   (str k "/" v)))
               m))))

(f-map {"foo" {"bar" "1" "baz" "2"}})
=> ["foo/bar/1" "foo/baz/2"]



回答5:


Here's my take:

(defn traverse [t]
  (letfn [(traverse- [path t]
            (when (seq t)
              (let [[x & xs] (seq t)
                    [k v] x]
                (lazy-cat
                  (if (map? v)
                    (traverse- (conj path k) v)
                    [[(conj path k) v]]) 
                  (traverse- path xs)))))]
    (traverse- [] t)))

(traverse {"foo" {"bar" "1" "baz" "2"}})
;=> [[["foo" "bar"] "1"] [["foo" "baz"] "2"]]

Traverse returns a lazy seq of path-leaf pairs. You can then apply any transformation to each path-leaf, for example to the "/path/to/leaf" fullpath form:

(def ->full-path #(->> (apply conj %) (clojure.string/join "/")))

(->> (traverse {"foo" {"bar" "1" "baz" "2"}})
     (map ->full-path))
;=> ("foo/bar/1" "foo/baz/2")

(->> (traverse {"foo" {"bar" {"buzz" 4 "fizz" "fuzz"} "baz" "2"} "faa" "fee"})
     (map ->full-path))
;=> ("foo/bar/buzz/4" "foo/bar/fizz/fuzz" "foo/baz/2" "faa/fee")


来源:https://stackoverflow.com/questions/31704704/depth-first-tree-traversal-accumulation-in-clojure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!