解题思路
- 利用递归,将目录转换成
{:name: ".", :children: []}
结构
- 对于第一层目录名,前缀装饰成
T_branch = "├── "
或者 L_branch = "└── "
- 对于子目录,前缀装饰成
I_branch = "│ "
或者SPACER = " "
举例如下:
.
├── 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 实现
#!/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 实现
(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 实现
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 实现
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
import java.io.File
val I_branch = "│ "
val T_branch = "├── "
val L_branch = "└── "
val SPACER = " "
data class Entry (val name: String, val children: List)
fun children(path: File): List {
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())
}
fun renderTree(tree: Entry): List {
val name = tree.name
val children = tree.children
return listOf(name) + children.mapIndexed { i, e -> prefix(children.size)(i, e) }.fold(listOf()) {l, r -> l + r}
}
fun prefix(size: Int): (Int, Entry) -> List {
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
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
#!/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
Rust
use std::env;
use std::path::Path;
use std::fs::{self, DirEntry};
struct Entry {
name: String,
children: Vec
}
fn main() {
let v = vec![1, 2, 3];
v.reverse();
// let args: Vec = env::args().collect();
// println!("{}", render_tree(&tree(Path::new(&args[1]))).join("\n"));
}
fn children(dir: &Path) -> Vec {
fs::read_dir(dir)
.expect("unable to read dir")
.into_iter()
.map(|e| e.expect("unable to get entry"))
.filter(|e| is_not_hidden(e))
.map(|e| e.path())
.map(|e| tree(&e))
.collect()
}
fn is_not_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| !s.starts_with("."))
.unwrap_or(false)
}
fn tree(path: &Path) -> Entry {
Entry{
name: path.file_name()
.and_then(|name| name.to_str())
.map_or(String::from("."),|str| String::from(str)),
children: if path.is_dir() {
children(path)
} else {
Vec::new()
}
}
}
fn render_tree(tree: &Entry) -> Vec {
let mut names = vec![String::from(&tree.name)];
let children = &tree.children;
let children: Vec<_> = children
.iter()
.enumerate()
.map(|(i, child)| decorate(children.len() - 1 == i, render_tree(child)))
.flatten()
.collect();
names.extend(children);
names
}
fn decorate(is_last: bool, children: Vec) -> Vec {
const I_BRANCH: &str = "│ ";
const T_BRANCH: &str = "├── ";
const L_BRANCH: &str = "└── ";
const SPACER: &str = " ";
let prefix_first = if is_last { L_BRANCH } else { T_BRANCH };
let prefix_rest = if is_last { SPACER } else { I_BRANCH };
let mut first = vec![format!("{}{}", prefix_first, children[0])];
first.extend(children[1..].iter().map(|child| format!("{}{}", prefix_rest, child)).collect::>());
first
}
$ rustc tree.rs
$ ./tree .
.
├── ./tree
├── ./tree.py
├── ./files.py
├── ./lists.py
├── ./tuples.py
├── ./resources
│ └── ./resources/README.md
├── ./recursion.py
└── ./data
├── ./data/output.txt
└── ./data/data.txt