파일 경로 n개로 폴더 나무 그림을 그리려 한다.
경로는 /로 나뉜 이름들이다. 마지막 이름이 파일이고, 그 앞의 이름들은 바깥에서 안쪽으로 들어가는 폴더다.
예를 들어 src/app/main.py는 src 폴더 안의 app 폴더 안에 있는 파일 main.py다. 경로가 같은 폴더 이름으로 시작하면 같은 폴더다.
첫 줄에는 맨 위 폴더를 뜻하는 .만 쓴다.
그 밑에, 한 폴더 안에 든 것들을 폴더 먼저, 파일 나중으로 쓴다. 폴더끼리, 파일끼리는 이름의 글자 코드 순(ASCII)으로 쓴다. 한 이름이 다른 이름의 앞부분이면 짧은 이름이 먼저다. 폴더를 한 줄 쓴 다음에는 바로 그 밑에 그 폴더 안의 것들을 같은 규칙으로 모두 쓰고 나서 다음 것으로 넘어간다.
한 줄은 앞머리, 연결표, 이름을 차례로 이은 문자열이다.
연결표는 그것이 들어 있는 폴더 안에서 마지막으로 쓰는 것이면 \-- (역슬래시, 빼기 둘, 빈칸 하나), 아니면 |-- 다.
맨 위 폴더 바로 안의 것은 앞머리가 빈 문자열이다.
어떤 폴더 줄의 앞머리가 P일 때, 그 폴더 안의 것들의 앞머리는 그 폴더가 마지막이었으면 P 뒤에 빈칸 네 개, 아니면 P 뒤에 |와 빈칸 세 개를 붙인 것이다.
폴더 이름 뒤에는 /를 붙인다.
맨 위 폴더가 아닌 폴더 안에 파일은 없고 폴더 하나만 들어 있으면, 두 폴더를 한 줄에 바깥/안/처럼 이어 쓴다. 이어 쓴 안쪽 폴더도 그렇다면 계속 잇는다. 그리고 마지막으로 이은 폴더 안의 것들을 이 줄 밑에 쓴다.
든 것이 파일 하나뿐인 폴더는 잇지 않는다. 맨 위 폴더 .도 잇지 않는다.
이어 쓴 줄을 늘어놓는 차례와 마지막인지는 맨 바깥 폴더의 이름으로 정한다.
첫 줄에 n, 다음 n줄에 경로가 하나씩 주어진다.
입력
1 ≤ n ≤ 500. 경로는 1~40글자이고, 이름은 1~10개다. 이름은 영어 소문자, 숫자, ., -, _로만 되어 있고 비어 있지 않으며 .이나 ..이 아니다. 경로는 서로 다르고, 어떤 경로의 파일이 다른 경로에서 폴더로 쓰이는 일은 없다.
실행하면 이 줄들이 차례로 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 sysclass 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 5001typedef 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"));