专栏首页编舟记命令行中 tree 的多重实现

命令行中 tree 的多重实现

解题思路

  1. 利用递归,将目录转换成 {:name: ".", :children: []} 结构
  2. 对于第一层目录名,前缀装饰成 T_branch = "├── "或者 L_branch = "└── "
  3. 对于子目录,前缀装饰成 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<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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 架构整洁之道导读(三)

    上回说到组件聚合,反映的是组件内部的“基本元素”的选择标准。第14章介绍的组件耦合则是指组件和组件之间的关系,这些依赖关系有些是好的,有些是不好的,我们即将看到...

    lambeta
  • Rust 入门 (Rust Rocks)

    做区块链的基本几乎没有人不知道 Rust 这门编程语言,它非常受区块链底层开发人员的青睐。说来也奇怪,Rust 起源于 Mazilla,唯一大规模应用就是 Fi...

    lambeta
  • 泛型编程

    泛型编程是一种编程风格,其中算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。

    lambeta
  • python练习题-day3

    16)从name变量对应的值中找到"N"对应的索引(如果找不到则报错),并输出结果

    郭耀华
  • C语言程序可以没有main函数

    学习C语言的同学都知道,每个C程序要有一个main函数,程序从main函数开始执行,在main函数中结束。但事实上,C程序也可以没有main函数,或者说自己可以...

    用户5807183
  • Capture QinQ Large Packets

    happy123.me
  • 普通学渣的春招,秋招历程以及实习心路

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

    大黄大黄大黄
  • Caffe2 - (六)CPU/GPU 模式切换

    在尝试把 Caffe 模型转换到 Caffe2,部署时 CPU/GPU 模式切换方法找了很久才找到一个用着可以,记录下.

    AIHGF
  • Perl 工作积累(不定期更新)

    学会用 perl -c 检查语法, perl -e 直接执行语句, perldoc 查看文档 , CPAN 查找 module ... # 注释; =pod ...

    s1mba
  • hadoop上搭建hive

    hadoop2.7.1+ubuntu 14.04 hive 2.0.1 集群环境 namenode节点:master (hive服务端) datanode 节点...

    王知无

扫码关注云+社区

领取腾讯云代金券