Tree command 的多重实现

解题思路

  1. 利用递归,将目录转换成 {:name: ".", :children: []} 结构
  2. 对于第一层目录名,前缀装饰成 T_branch = "├── " 或者 L_branch = "└── "
  3. 对于子目录,前缀装饰成 I_branch = "│ "或者SPACER = " "
    举例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .
    ├── tree.py # 不是最后一项,所以使用 T_branch 前缀
    ├── files.py
    ├── lists.py
    ├── tuples.py
    ├── resources
    │ └── README.md # 由于其父亲不是最后一项,所以使用 I_branch 前缀
    ├── recursion.py
    └── data # 是最后一项,所以使用 L_branch 前缀
    ├── output.txt # 由于其父亲是最后一项,所以使用 SPACE 前缀
    └── data.txt

python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/local/bin/python
# -*- coding: UTF-8 -*-

"""
list a directory in tree way.

children(path):
map(lambda name: tree(path, name), listdir(path))

tree(parent, dir_name):
if is_file(parent, dir_name):
return {'name': dir_name, 'children': []}
else:
children = children(join(parent, dir_name))
return {'name': dir_name, 'children': children}
"""
import os
import functools as fp

I_branch = "│ "
T_branch = "├── "
L_branch = "└── "
SPACER = " "

def _children(path):
return map(lambda filename: tree_format(path, filename), os.listdir(path))

def tree_format(parent, dir_name):
path = os.path.join(parent, dir_name)
is_file = os.path.isfile(path)
children = [] if is_file else _children(path)
return {'name': dir_name, 'children': list(children)}

def render_tree(tr):
name = tr['name']
children = tr['children']
return [name] + fp.reduce(lambda l, r: l + r,
map(lambda arg: render(len(children))(*arg),
enumerate(children)),
[])

def render(length):
def prefix(index, child):
is_last = (index == length - 1)
prefix_first = L_branch if is_last else T_branch
prefix_rest = SPACER if is_last else I_branch
tr = render_tree(child)
head = prefix_first + tr[0]
tail = [prefix_rest + t for t in tr[1:]]
return [head] + tail
return prefix

if __name__ == "__main__":
print '\n'.join(render_tree(tree('', sys.argv[1])))

$ python3 tree.py . #打印当前的目录的所有文件及子目录
.
├── tree.py
├── files.py
├── lists.py
├── tuples.py
├── resources
│ └── README.md
├── recursion.py
└── data
├── output.txt
└── data.txt

Clojure 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
(ns tree
(:require [clojure.java.io :as io]
[clojure.string :as str]))
(def L-branch "└── ")
(def T-branch "├── ")
(def I-branch "│ ")
(def SPACE " ")

(declare tree)

(defn children [path]
(map #(tree %) (.listFiles path)))

(defn tree [dir-name]
(let [path (io/file dir-name)
dir? (.isDirectory path)]
{:name (.getName path)
:children (if dir? (children path))}))

(defn render-tree [{name :name children :children}]
(cons name
(mapcat (fn [child index]
(let [last? (= index (dec (count children)))
prefix-first (if last? L-branch T-branch)
prefix-rest (if last? SPACE I-branch)
sub-tree (render-tree child)]
(cons (str prefix-first (first sub-tree))
(map #(str prefix-rest %) (rest sub-tree)))))
children
(range))))

(defn -main [& args]
(->>
(tree (first args))
(render-tree)
(str/join "\n")
(println)))
$ lein run -m tree .
.
├── tree.py
├── files.py
├── lists.py
├── tuples.py
├── resources
│ └── README.md
├── recursion.py
└── data
├── output.txt
└── data.txt

Golang 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package main

import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
)

const (
I_branch = "│ "
T_branch = "├── "
L_branch = "└── "
SPACER = " "
)

type entry struct {
name string
children []entry
}

func (e entry) String() string {
if len(e.children) == 0 {
return e.name
} else {
s := e.name
for _, child := range e.children {
s += child.String()
}

return s
}
}

func Children(path string) []entry {
result := []entry{}
files, _ := ioutil.ReadDir(path)
for _, f := range files {
result = append(result, Tree(path, f.Name()))
}

return result
}

func Tree(parent, dirName string) entry {
realPath := path.Join(parent, dirName)
theChildren := []entry{}
if f, ok := os.Stat(realPath); ok == nil {
if f.IsDir() {
theChildren = Children(realPath)
}
}
return entry{name: dirName, children: theChildren}
}

func RenderTree(e entry) []string {
name := e.name
children := e.children
result := []string{name}

for index, child := range children {
subTree := RenderTree(child)
prefixFirst := T_branch
prefixRest := I_branch
if index == len(children)-1 {
prefixFirst = L_branch
prefixRest = SPACER
}

result = append(result, prefixFirst+subTree[0])

for _, sub := range subTree[1:] {
result = append(result, prefixRest+sub)
}
}
return result
}

func main() {
fmt.Println(strings.Join(RenderTree(Tree("", os.Args[1])), "\n"))
}
$ go run tree.go .
.
├── data
│ ├── data.txt
│ └── output.txt
├── files.py
├── lists.py
├── recursion.py
├── resources
│ └── README.md
├── tree.py
└── tuples.py

NodeJS 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const path = require('path')
const fs = require('fs')
const I_branch = '│ '
const T_branch = '├── '
const L_branch = '└── '
const SPACER = ' '

function children(path) {
return fs.readdirSync(path).map(filename => tree(path, filename))
}

function tree(parentDir, dirName) {
let realPath = path.join(parentDir, dirName)
let isDir = fs.statSync(realPath).isDirectory()
return {name: dirName, children: isDir ? children(realPath) : []}
}

function prefix(len) {
return (tr, index) => {
let isLast = len == index + 1
let prefixFirst = isLast ? L_branch : T_branch
let prefixRest = isLast ? SPACER : I_branch
let [head, ...tail]= renderTree(tr)

return [prefixFirst + head].concat(tail.map(name => prefixRest + name))
}
}

function renderTree({name: name, children: children}) {
return [name]
.concat(children
.map(prefix(children.length))
.reduce((l, r) => l.concat(r), []))
}

console.log(renderTree(tree('', process.argv[2])).join('\n'))

$ node tree.js .
.
├── data
│ ├── data.txt
│ └── output.txt
├── files.py
├── lists.py
├── recursion.py
├── resources
│ └── README.md
├── tree.py
└── tuples.py

Kotlin script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.File

val I_branch = "│ "
val T_branch = "├── "
val L_branch = "└── "
val SPACER = " "

data class Entry (val name: String, val children: List<Entry>)

fun children(path: File): List<Entry> {
return path.listFiles().map {tree(it)}
}

fun tree(path: File): Entry {
val isDir = path.isDirectory()
return Entry(path.getName(), if(isDir) children(path) else listOf<Entry>())
}

fun renderTree(tree: Entry): List<String> {
val name = tree.name
val children = tree.children

return listOf(name) + children.mapIndexed { i, e -> prefix(children.size)(i, e) }.fold(listOf<String>()) {l, r -> l + r}
}

fun prefix(size: Int): (Int, Entry) -> List<String> {
return {index, entry ->
val isLast = index + 1 == size
val prefixFirst = if(isLast) L_branch else T_branch
val prefixRest = if(isLast) SPACER else I_branch
val subTree = renderTree(entry)

listOf(prefixFirst + subTree.first()) + subTree.drop(1).map {t -> prefixRest + t}
}
}

println(renderTree(tree(File(args[0]))).joinToString("\n"))

$ kotlinc -script tree.kts .
.
├── tree.py
├── files.py
├── lists.py
├── tuples.py
├── resources
│ └── README.md
├── recursion.py
└── data
├── output.txt
└── data.txt

Scala

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.io._
val I_branch = "│ "
val T_branch = "├── "
val L_branch = "└── "
val SPACER = " "

case class Entry(name: String, children: List[Entry])

def children(path: File): List[Entry] = path.listFiles().toList.map((it: File) => tree(it))

def tree(path: File): Entry = Entry(path.getName(), if(path.isDirectory()) children(path) else List[Entry]())

def prefix(size: Int) = (index: Int, entry: Entry) => {
val isLast = index + 1 == size
val prefixFirst = if(isLast) L_branch else T_branch
val prefixRest = if(isLast) SPACER else I_branch
val subTree = renderTree(entry)
List(prefixFirst + subTree.head) ++ subTree.tail.map(t => prefixRest + t)
}

def renderTree(tree: Entry): List[String] = {
val name = tree.name
val children = tree.children

return List(name) ++ children
.zipWithIndex
.map({case (e: Entry, i: Int) => prefix(children.size)(i, e)})
.fold(List[String]())((l, r) => l ++ r)
}

println(renderTree(tree(new File(args(0)))).mkString("\n"))

$ scala tree.scala .
.
├── tree.py
├── files.py
├── lists.py
├── tuples.py
├── resources
│ └── README.md
├── recursion.py
└── data
├── output.txt
└── data.txt

Elixir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/usr/bin/env elixir

defmodule Tree do
def main([dir | _]) do
dir |> tree_format |> render_tree |> Enum.join("\n") |> IO.puts
end

defp children(path) do
if (path |> File.dir?) do
File.ls!(path) |> Enum.map(fn f -> tree_format(path, f) end)
else
[]
end
end

defp tree_format(parent_dir \\ ".", dir_name) do
%{:name => dir_name, :children => Path.join(parent_dir, dir_name) |> children}
end

defp decorate(is_last?, [parent | children]) do
prefix_first = (if (is_last?), do: "└── ", else: "├── ")
prefix_rest = (if (is_last?), do: " ", else: "│ ")
[prefix_first <> parent | children |> Enum.map(fn child -> prefix_rest <> child end)]
end

defp render_tree(%{name: dir_name, children: children}) do
[dir_name
| children
|> Enum.with_index(1)
|> Enum.map(fn {child, index} -> decorate(length(children) == index, render_tree(child)) end)
|> List.flatten]
end

end

Tree.main(System.argv)

$ elixir tree.exs .
.
├── tree.py
├── files.py
├── lists.py
├── tuples.py
├── resources
│ └── README.md
├── recursion.py
└── data
├── output.txt
└── data.txt