{ }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"));