【问题标题】:Clojure - Convert File Path to TreeClojure - 将文件路径转换为树
【发布时间】:2018-03-28 22:43:34
【问题描述】:

我是 Clojure 的新手,我正在努力研究如何使用文件路径在 Clojure 中创建树。我使用 file-seq 获取目录下的所有文件并将它们存储在 files 中。输入是一个文件路径,如下所示:

resources/data/2012/05/02/low.xml
resources/data/2012/05/01/low.xml

我可以使用以下方法获取文件夹和文件的所有单独名称:

(for [x files] 
 (if (.contains (.getPath x) ".json")
  (for [y (str/split (.getPath x) #"\\")] y)))

这给了我所有文件夹的列表,但我不知道如何将它们组合成 1 个列表以创建树结构。如果有任何答案可以解释他们的代码如何工作,以帮助学习。这 2 个输入的期望输出为:

(resources (data (2012 (05 (02 (low.xml)) (01 (low.xml))))))

【问题讨论】:

  • 你想要的输出是什么?
  • 我会把它添加到问题中,这会很有帮助

标签: clojure tree


【解决方案1】:

你需要构建树是这样的:

(defn as-tree [data]
  (map (fn [[k vs]] (cons k (as-tree (keep next vs))))
       (group-by first data)))

给定已解析路径的列表(或通常任何序列),它将创建您的结构:

user> (as-tree [["resources" "data" "2012" "05" "02" "low.xml"]
                ["resources" "data" "2012" "05" "01" "aaa.xml"]
                ["resources" "data" "2012" "05" "02" "high.xml"]
                ["resources" "data" "2012" "05" "01" "xsxs.xml"]
                ["resources" "data" "2012" "06" "01" "bbb.xml"]
                ["resources" "data" "2012" "05" "01" "ccc.xml"]
                ["resources" "data" "2012" "02" "some.xml"]
                ["resources" "data" "2012" "01" "some2.xml"]
                ["other-resources" "data" "2015" "10" "some100.xml"]])

;; (("resources" 
;;   ("data" 
;;     ("2012" 
;;       ("05" 
;;         ("02" ("low.xml") 
;;               ("high.xml")) 
;;         ("01" ("aaa.xml") 
;;               ("xsxs.xml") 
;;               ("ccc.xml"))) 
;;       ("06" 
;;         ("01" ("bbb.xml"))) 
;;       ("02" ("some.xml")) 
;;       ("01" ("some2.xml"))))) 
;;  ("other-resources" ("data" ("2015" ("10" ("some100.xml"))))))

所以在您的情况下,它可能看起来像这样(项目中.clj 文件的树):

(require '[clojure.string :as cs])
(import 'java.io.File)

(->> (File. ".")
     file-seq
     (map #(.getPath %))
     (filter #(cs/ends-with? % ".clj"))
     (map #(cs/split % (re-pattern File/separator)))
     as-tree
     first)

;;=> ("." 
;;     ("src" 
;;       ("playground" 
;;         ("core.clj"))) 
;;     ("test" 
;;       ("playground" 
;;         ("core_test.clj"))) 
;;     ("project.clj"))

【讨论】:

    【解决方案2】:

    一种方法是按此处所述的顺序遍历目录:https://docs.oracle.com/javase/tutorial/essential/io/walk.html

    另一种方法是将路径名拆分为其组件字符串后累积结果,然后重复使用assoc-in

    (ns tst.demo.core
      (:use demo.core tupelo.core tupelo.test)
      (:require [clojure.string :as str] ))
    
    (defn accum-tree
      "Accumulates a file path into a map tree"
      [file-elem-tree path-str]
      (let [path-elems (str/split path-str #"/")
            key-seq    (butlast path-elems)
            file-name  (last path-elems)]
        (assoc-in file-elem-tree key-seq file-name)))
    

    accum-tree 的每次调用都是这样的:

    path-elems  => ["resources" "data" "2012" "05" "02" "low.xml"]
    key-seq     => ("resources" "data" "2012" "05" "02")
    file-name   =>  "low.xml" 
    

    单元测试显示最终结果的地方。

    (dotest
      (let [file-strings ["resources/data/2012/05/02/low.xml"
                          "resources/data/2012/05/01/low.xml"]]
        (is= (reduce accum-tree {} file-strings)
          {"resources"
           {"data"
            {"2012"
             {"05"
              {"02" "low.xml",
               "01" "low.xml"}}}}})))
    

    【讨论】:

      【解决方案3】:

      使用给定的文件/目录结构:

      /tmp/root
      ├── file1.txt
      ├── file2.txt
      ├── sub
      │   ├── file5.txt
      │   └── file6.txt
      └── sub1
          ├── emptysub
          ├── file3.txt
          ├── file4.txt
          └── subsub
              └── file99.txt
      

      在给定这些路径的情况下,这是一种使用拉链(从一棵空树)构建的方法:

      (def paths
        (for [x (file-seq (io/file "/tmp/root"))]
          (keep not-empty (str/split (.getPath x) #"/"))))
      
      (defn level-loc [loc v] ;; find node with value at same depth as loc
        (loop [l loc]
          (when l
            (let [n (z/node l)]
              (cond
                (= n v) l
                (and (coll? n) (= (first n) v)) (-> l z/down)
                :else (recur (-> l z/right)))))))
      
      (defn graft-path [loc path]
        (reduce
          (fn [[_ path :as loc] p]
            (or (level-loc loc p) ;; find existing node
                (if (nil? path)
                  ;; appends at top of tree
                  (-> loc
                      (z/append-child p)
                      z/down)
                  ;; inserts at depth
                  (-> loc
                      (z/insert-right (list p))
                      z/right
                      z/down))))
          loc
          path))
      
      (defn paths->tree [paths]
        (z/root
          (reduce
            (comp z/seq-zip z/root graft-path)
            (z/seq-zip '())
            paths)))
      

      产生以下输出:

      (paths->tree paths)
      =>
      ("tmp"
       ("root"
        ("sub" ("file6.txt") ("file5.txt"))
        ("sub1" ("emptysub") ("subsub" ("file99.txt")) ("file4.txt") ("file3.txt"))
        ("file1.txt")
        ("file2.txt")))
      

      【讨论】:

      • 我应该注意这个答案更多的是关于解决问题用拉链,而不是简单/清晰/简洁地解决问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-22
      • 1970-01-01
      • 1970-01-01
      • 2016-12-11
      • 1970-01-01
      • 2011-07-28
      • 2014-07-19
      相关资源
      最近更新 更多