{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

줄 하나를 앞머리 + 연결표 + 이름으로 만들어 이어 붙이는 문제예요.
앞머리는 재귀 함수에 문자열로 넘기면서 깊이마다 네 글자씩 늘려요: Python·JS·TS는 prefix + "| "로 새 문자열을 만들어 넘기고, Java는 StringBuilder, C#은 StringBuilder로 줄을 모아 한 번에 출력해요(문자열 +=를 수천 번 하면 매번 새로 복사해요).
C는 char prefix[64] 하나에 네 글자를 memcpy로 붙였다가, 돌아오면 prefix[plen] = '\0'으로 잘라 써요.
크레이콘은 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, 크레이콘 정렬().
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"));