{ }codelper
拼接构建字符串递归自定义排序P0248

画文件夹树

要根据 n 条文件路径画出文件夹树。
路径是用 / 隔开的一串名字。
最后一个名字是文件,前面的名字是从外往里的文件夹。
例如 src/app/main.py 是 src 文件夹里的 app 文件夹中的文件 main.py。
以相同文件夹名开头的路径指的是同一个文件夹。
第一行只写表示最上层文件夹的 .。
在它下面,写一个文件夹里的内容时文件夹在前、文件在后。
文件夹之间、文件之间按名字的字符编码顺序(ASCII)写。
一个名字是另一个名字的开头部分时,短的名字在前。
写完一个文件夹的那一行后,紧接着在下面按同样的规则写完它里面的全部内容,再写下一项。
一行是依次连接前缀、连接符和名字得到的字符串。
如果它是所在文件夹里最后写的一项,连接符是 \-- (一个反斜杠、两个减号、一个空格),否则是 |-- 。
最上层文件夹里直接的内容,前缀是空字符串。
某个文件夹那一行的前缀是 P 时,它里面内容的前缀是:该文件夹是最后一项则为 P 加四个空格,否则为 P 加 | 和三个空格。
文件夹名后面加 /。
如果一个不是最上层的文件夹里没有文件、只有一个文件夹,就把这两个文件夹写在同一行,如 外/内/。
被接上的里层文件夹如果也是这样,就继续接。
然后在这一行下面写最后接上的文件夹里的内容。
里面只有一个文件的文件夹不接。
最上层文件夹 . 也不接。
接起来的那一行的排列顺序以及是否为最后一项,按最外层文件夹的名字决定。
第一行给出 n,接下来 n 行每行一条路径。

输入
1 ≤ n ≤ 500。
路径长 1~40 个字符,由 1~10 个名字组成。
名字只由英文小写字母、数字、.、-、_ 组成,不为空,也不是 . 或 ..。
路径互不相同,某条路径中的文件不会在另一条路径中被当作文件夹。
输出
树形图的各行(第一行是 .)
样例 1 · stdin
7
src/app/main.py
src/app/util.py
src/readme.md
docs/guide/intro/start.md
build.sh
a-b.txt
src/lib/x/y/z.c
stdout
.
|-- docs/guide/intro/
|   \-- start.md
|-- src/
|   |-- app/
|   |   |-- main.py
|   |   \-- util.py
|   |-- lib/x/y/
|   |   \-- z.c
|   \-- readme.md
|-- a-b.txt
\-- build.sh
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

本题把每一行做成 前缀 + 连接符 + 名字 再连起来。
前缀以字符串传给递归函数,每深一层加四个字符:Python、JS、TS 用 prefix + "| " 生成新字符串传下去;Java、C# 用 StringBuilder 收集各行一次输出(对字符串做几千次 += 每次都要复制)。
C 只用一个 char prefix[64],用 memcpy 接上四个字符,返回时用 prefix[plen] = '\0' 截回。
Craykon 传新字符串 prefix + more,把各行收进列表,用 lines.连接("\n") 一次写出。
字符串字面量里的反斜杠要写两次,如 "\\-- "。
按字符编码排序:Python sorted,JS、TS 默认的 sort()(UTF-16 编码顺序),Java TreeMap、Collections.sort,C# StringComparer.Ordinal(默认比较可能遵循区域文化规则),C strcmp,C++ std::map、std::sort,Craykon 排序()。
JS 的 localeCompare 是人类词典顺序,-、.、_ 的位置不同。
五个陷阱:① 在最后一个文件夹下面也画 | ,已经结束的线会继续往下延伸;是否为最后一项要放进前缀传下去。
② 不接只含一个文件夹的文件夹,或 ③ 连只含一个文件的文件夹也接上,都是错的。
④ 最上层的 . 不接。
⑤ 把文件夹和文件混在一起只按名字排序,z/ 文件夹就会排到 a 文件后面。

Pythonmain.py · 43 行
import sys  class Folder:    def __init__(self):        self.folders: dict[str, "Folder"] = {}        self.files: list[str] = []  def draw(folder, prefix, out):    entries = [(name, True) for name in sorted(folder.folders)] + [(name, False) for name in sorted(folder.files)]    for i, (name, is_folder) in enumerate(entries):        last = i == len(entries) - 1        mark = "\\-- " if last else "|-- "        if not is_folder:            out.append(prefix + mark + name)            continue        label, inner = name, folder.folders[name]        # 든 것이 폴더 하나뿐이면 한 줄에 이어 쓴다        while len(inner.folders) == 1 and not inner.files:            (child, sub), = inner.folders.items()            label += "/" + child            inner = sub        out.append(prefix + mark + label + "/")        draw(inner, prefix + ("    " if last else "|   "), out)  def main():    lines = sys.stdin.read().split("\n")    n = int(lines[0])    root = Folder()    for path in lines[1:1 + n]:        parts = path.strip().split("/")        cur = root        for name in parts[:-1]:            cur = cur.folders.setdefault(name, Folder())        cur.files.append(parts[-1])    out = ["."]    draw(root, "", out)    print("\n".join(out))  main()
JavaScriptmain.js · 37 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const newFolder = () => ({ folders: new Map(), files: [] });const root = newFolder();for (let i = 1; i <= n; i++) {  const parts = lines[i].trim().split("/");  let cur = root;  for (const name of parts.slice(0, -1)) {    if (!cur.folders.has(name)) cur.folders.set(name, newFolder());    cur = cur.folders.get(name);  }  cur.files.push(parts[parts.length - 1]);}const out = ["."];// 기본 sort()는 문자열을 UTF-16 코드 순으로 늘어놓는다(ASCII 이름이면 글자 코드 순)function draw(folder, prefix) {  const entries = [...[...folder.folders.keys()].sort().map((name) => [name, true]), ...[...folder.files].sort().map((name) => [name, false])];  entries.forEach(([name, isFolder], i) => {    const last = i === entries.length - 1;    const mark = last ? "\\-- " : "|-- ";    if (!isFolder) {      out.push(prefix + mark + name);      return;    }    let label = name;    let inner = folder.folders.get(name);    while (inner.folders.size === 1 && inner.files.length === 0) {      const [child, sub] = [...inner.folders][0];      label += "/" + child;      inner = sub;    }    out.push(prefix + mark + label + "/");    draw(inner, prefix + (last ? "    " : "|   "));  });}draw(root, "");console.log(out.join("\n"));
TypeScriptmain.ts · 48 行
interface Folder {  folders: Map<string, Folder>;  files: string[];} const lines: string[] = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const newFolder = (): Folder => ({ folders: new Map(), files: [] });const root = newFolder();for (let i = 1; i <= n; i++) {  const parts = lines[i].trim().split("/");  let cur = root;  for (const name of parts.slice(0, -1)) {    let next = cur.folders.get(name);    if (next === undefined) {      next = newFolder();      cur.folders.set(name, next);    }    cur = next;  }  cur.files.push(parts[parts.length - 1]);}const out: string[] = ["."];function draw(folder: Folder, prefix: string): void {  const entries: [string, boolean][] = [    ...[...folder.folders.keys()].sort().map((name): [string, boolean] => [name, true]),    ...[...folder.files].sort().map((name): [string, boolean] => [name, false]),  ];  entries.forEach(([name, isFolder], i) => {    const last = i === entries.length - 1;    const mark = last ? "\\-- " : "|-- ";    if (!isFolder) {      out.push(prefix + mark + name);      return;    }    let label = name;    let inner = folder.folders.get(name)!;    while (inner.folders.size === 1 && inner.files.length === 0) {      const [child, sub] = [...inner.folders][0];      label += "/" + child;      inner = sub;    }    out.push(prefix + mark + label + "/");    draw(inner, prefix + (last ? "    " : "|   "));  });}draw(root, "");console.log(out.join("\n"));
JavaMain.java · 50 行
import java.io.*;import java.util.*; public class Main {    static class Folder {        // TreeMap은 열쇠를 compareTo 순(ASCII 이름이면 글자 코드 순)으로 들고 있다        final TreeMap<String, Folder> folders = new TreeMap<>();        final List<String> files = new ArrayList<>();    }     static final StringBuilder out = new StringBuilder();     static void draw(Folder folder, String prefix) {        List<String> files = new ArrayList<>(folder.files);        Collections.sort(files);        int total = folder.folders.size() + files.size();        int i = 0;        for (Map.Entry<String, Folder> e : folder.folders.entrySet()) {            boolean last = ++i == total;            StringBuilder label = new StringBuilder(e.getKey());            Folder inner = e.getValue();            while (inner.folders.size() == 1 && inner.files.isEmpty()) {                Map.Entry<String, Folder> only = inner.folders.firstEntry();                label.append('/').append(only.getKey());                inner = only.getValue();            }            out.append(prefix).append(last ? "\\-- " : "|-- ").append(label).append("/\n");            draw(inner, prefix + (last ? "    " : "|   "));        }        for (String name : files) {            boolean last = ++i == total;            out.append(prefix).append(last ? "\\-- " : "|-- ").append(name).append('\n');        }    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine().trim());        Folder root = new Folder();        for (int k = 0; k < n; k++) {            String[] parts = br.readLine().trim().split("/");            Folder cur = root;            for (int j = 0; j < parts.length - 1; j++) cur = cur.folders.computeIfAbsent(parts[j], x -> new Folder());            cur.files.add(parts[parts.length - 1]);        }        out.append(".\n");        draw(root, "");        System.out.print(out);    }}
C#Main.cs · 68 行
using System;using System.Collections.Generic;using System.Text; class Folder{    // 문자열 비교는 문화권 규칙이 아니라 글자 코드 순(Ordinal)으로    public SortedDictionary<string, Folder> Folders = new SortedDictionary<string, Folder>(StringComparer.Ordinal);    public List<string> Files = new List<string>();} class Program{    static readonly StringBuilder Out = new StringBuilder();     static void Draw(Folder folder, string prefix)    {        var files = new List<string>(folder.Files);        files.Sort(StringComparer.Ordinal);        int total = folder.Folders.Count + files.Count;        int i = 0;        foreach (var e in folder.Folders)        {            bool last = ++i == total;            var label = new StringBuilder(e.Key);            Folder inner = e.Value;            while (inner.Folders.Count == 1 && inner.Files.Count == 0)            {                foreach (var only in inner.Folders)                {                    label.Append('/').Append(only.Key);                    inner = only.Value;                }            }            Out.Append(prefix).Append(last ? "\\-- " : "|-- ").Append(label).Append("/\n");            Draw(inner, prefix + (last ? "    " : "|   "));        }        foreach (var name in files)        {            bool last = ++i == total;            Out.Append(prefix).Append(last ? "\\-- " : "|-- ").Append(name).Append('\n');        }    }     static void Main()    {        int n = int.Parse(Console.ReadLine().Trim());        var root = new Folder();        for (int k = 0; k < n; k++)        {            string[] parts = Console.ReadLine().Trim().Split('/');            Folder cur = root;            for (int j = 0; j < parts.Length - 1; j++)            {                if (!cur.Folders.TryGetValue(parts[j], out Folder next))                {                    next = new Folder();                    cur.Folders[parts[j]] = next;                }                cur = next;            }            cur.Files.Add(parts[parts.Length - 1]);        }        Out.Append(".\n");        Draw(root, "");        Console.Write(Out);    }}
C++main.cpp · 64 行
#include <algorithm>#include <iostream>#include <map>#include <sstream>#include <string>#include <vector> struct Folder {    std::map<std::string, int> folders; // 이름 → folders 목록의 자리. map은 이름을 글자 코드 순으로 들고 있다    std::vector<std::string> files;}; std::vector<Folder> all(1); // 0번이 맨 위 폴더std::string out; void draw(int v, const std::string &prefix) {    std::vector<std::string> files = all[v].files;    std::sort(files.begin(), files.end());    size_t total = all[v].folders.size() + files.size(), i = 0;    for (const auto &[name, at] : all[v].folders) {        bool last = ++i == total;        std::string label = name;        int inner = at;        while (all[inner].folders.size() == 1 && all[inner].files.empty()) {            const auto &only = *all[inner].folders.begin();            label += "/" + only.first;            inner = only.second;        }        out += prefix + (last ? "\\-- " : "|-- ") + label + "/\n";        draw(inner, prefix + (last ? "    " : "|   "));    }    for (const auto &name : files) {        bool last = ++i == total;        out += prefix + (last ? "\\-- " : "|-- ") + name + "\n";    }} int main() {    int n;    std::cin >> n;    for (int i = 0; i < n; i++) {        std::string path, part;        std::cin >> path;        std::vector<std::string> parts;        std::stringstream ss(path);        while (std::getline(ss, part, '/')) parts.push_back(part);        int cur = 0;        for (size_t j = 0; j + 1 < parts.size(); j++) {            auto it = all[cur].folders.find(parts[j]);            if (it == all[cur].folders.end()) {                all.emplace_back(); // 여기서 all이 옮겨질 수 있으니 참조를 쥐지 않고 자리 번호로 다룬다                all[cur].folders[parts[j]] = (int)all.size() - 1;                cur = (int)all.size() - 1;            } else {                cur = it->second;            }        }        all[cur].files.push_back(parts.back());    }    out = ".\n";    draw(0, "");    std::cout << out;    return 0;}
Cmain.c · 83 行
#include <stdio.h>#include <stdlib.h>#include <string.h> #define MAXNODE 5001 typedef struct {    char name[41];    int is_folder;    int first, next; /* 첫 아이와 다음 형제(없으면 -1) */    int kids;        /* 든 것의 수 */} Node; static Node nodes[MAXNODE];static int count = 1; /* 0번은 맨 위 폴더 */static char prefix[64]; /* 앞머리: 깊이마다 네 글자씩 붙였다가 떼어 낸다 */ static int child(int parent, const char *name, int is_folder) {    for (int c = nodes[parent].first; c >= 0; c = nodes[c].next)        if (strcmp(nodes[c].name, name) == 0) return c;    Node *nd = &nodes[count];    strcpy(nd->name, name);    nd->is_folder = is_folder;    nd->first = -1;    nd->kids = 0;    nd->next = nodes[parent].first;    nodes[parent].first = count;    nodes[parent].kids++;    return count++;} /* 폴더가 먼저, 같은 종류끼리는 글자 코드 순(strcmp) */static int by_kind_then_name(const void *pa, const void *pb) {    const Node *a = &nodes[*(const int *)pa], *b = &nodes[*(const int *)pb];    if (a->is_folder != b->is_folder) return b->is_folder - a->is_folder;    return strcmp(a->name, b->name);} static void draw(int v, size_t plen) {    int k = nodes[v].kids, i = 0;    int *list = malloc(sizeof(int) * (size_t)k);    for (int c = nodes[v].first; c >= 0; c = nodes[c].next) list[i++] = c;    qsort(list, (size_t)k, sizeof(int), by_kind_then_name);    for (i = 0; i < k; i++) {        int c = list[i], last = i == k - 1;        prefix[plen] = '\0';        printf("%s%s%s", prefix, last ? "\\-- " : "|-- ", nodes[c].name);        if (!nodes[c].is_folder) {            printf("\n");            continue;        }        while (nodes[c].kids == 1 && nodes[nodes[c].first].is_folder) {            c = nodes[c].first;            printf("/%s", nodes[c].name);        }        printf("/\n");        memcpy(prefix + plen, last ? "    " : "|   ", 5); /* 끝의 '\0'까지 */        draw(c, plen + 4);    }    free(list);} int main(void) {    int n;    char line[64];    nodes[0].first = -1;    nodes[0].kids = 0;    nodes[0].is_folder = 1;    scanf("%d", &n);    for (int i = 0; i < n; i++) {        scanf("%63s", line);        int cur = 0;        char *part = line;        for (char *slash; (slash = strchr(part, '/')) != NULL; part = slash + 1) {            *slash = '\0';            cur = child(cur, part, 1);        }        child(cur, part, 0);    }    printf(".\n");    draw(0, 0);    return 0;}
Craykonmain.cray · 71 行
// 폴더 하나를 사전 하나로: "folders"(이름 → 폴더 사전)와 "files"(파일 이름 목록)새폴더(){    f = 字典();    f["folders"] = 字典();    f["files"] = [];    返回 f;} lines = []; // 그리기: 폴더 안의 것을 앞머리 prefix를 붙여 한 줄씩 lines에 넣는다그리기(folder, prefix){    dirs = folder["folders"].键列表;    dirs.排序(); // 문자열은 글자 코드 순    files = folder["files"].子列表(0, folder["files"].数量);    files.排序();    total = dirs.数量 + files.数量;    循环 (i = 0; i < dirs.数量; i++)    {        last = i == total - 1;        mark = "|-- ";        more = "|   ";        如果 (last)        {            mark = "\\-- ";            more = "    ";        }        label = dirs[i];        inner = folder["folders"][dirs[i]];        // 든 것이 폴더 하나뿐이면 한 줄에 이어 쓴다        当 (inner["folders"].数量 == 1 && inner["files"].数量 == 0)        {            only = inner["folders"].键列表[0];            label = label + "/" + only;            inner = inner["folders"][only];        }        lines.添加(prefix + mark + label + "/");        그리기(inner, prefix + more);    }    循环 (j = 0; j < files.数量; j++)    {        mark = "|-- ";        如果 (dirs.数量 + j == total - 1)        {            mark = "\\-- ";        }        lines.添加(prefix + mark + files[j]);    }} n = 控制台.读行().去空白().转数字();root = 새폴더();循环 (k = 0; k < n; k++){    parts = 控制台.读行().去空白().分割("/");    cur = root;    循环 (j = 0; j < parts.数量 - 1; j++)    {        如果 (!cur["folders"].包含(parts[j]))        {            cur["folders"][parts[j]] = 새폴더();        }        cur = cur["folders"][parts[j]];    }    cur["files"].添加(parts[parts.数量 - 1]);}lines.添加(".");그리기(root, "");控制台.输出(lines.连接("\n"));