The first line gives the number of lines n, followed by n lines of a Markdown document.
A line is a heading if it starts with 1 to 6 # characters and the very next character is a space.
The number of # characters is the heading's level.
A line with no space after the # (like "#tag"), a line with 7 or more #, a line that starts with a space, and a line that is just "#" are not headings.
The heading text is the rest of the line after the # characters, with all spaces at both ends removed (inner spaces stay as they are).
Make a slug (link name) for each heading.
Turn English uppercase letters in the heading text into lowercase, turn each single space into one -, and delete every character that is not a lowercase English letter, a digit, - or _.
Call the result the base name.
If it equals a slug already used by an earlier heading, try appending -1, -2, -3 … to the base name in turn, and use the first name nobody has used yet.
Used slugs include the ones made this way with a number.
Print the table of contents, one line per heading, in the order the headings appear.
Each line is 2×(level−1) spaces followed by "- [heading text](#slug)".
Input
1 ≤ n ≤ 200. Each line has length 0 to 100 and uses only ASCII characters (codes 32 to 126). Square brackets [ ] and parentheses ( ) do not appear. There is at least one heading, and each heading line has at least one English letter or digit after the #
Output
one line per heading: the indentation and "- [heading text](#slug)"
Example 1 · stdin
6
# Hello World
#tag
## Hello World
### C# & Rust!
## hello-world
# Hello World
When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).
$ Ready
Open this on a computer to write and run code. On mobile you can read the problem and the solutions.
In other languagesShow solutions by language · 8 languages
There are four traps. First, without trimming, the trailing spaces of "# Hello World " become -- at the end of the slug. Trimming is Python strip(), JS/TS/Java trim(), C# Trim(), Craykon Trim(); C narrows two pointers from both ends and C++ cuts with find_first_not_of/find_last_not_of. Second, if you only count how often each base name appeared (the second "a" becomes a-1), then "a", "a", "a-1" prints a-1 twice. Put every slug used so far into a set and raise the number until the name is free. Sets are Python set, JS/TS Set, Java/C# HashSet, C++ unordered_set; C has none, so scan an array from the start (200 entries is fast enough). Craykon stores used[slug] = true in a Dict() and asks used.Contains(slug). Third, if you do not check for the space after the #, "#tag" becomes a heading too. The JS regex /^(#{1,6}) / rejects seven # by itself, because a # sits where the space should be. Fourth, keeping only a-z before lowercasing loses the uppercase letters. Lowercasing is Python lower(), JS/TS toLowerCase(), Java toLowerCase(Locale.ROOT) (so I does not become a dotless ı under a Turkish locale), C# ToLowerInvariant(), C/C++ tolower per character, Craykon ToLower().
Pythonmain.py · 25 lines
def make_slug(title): slug = "" for ch in title.lower(): if ch == " ": slug += "-" elif ch in "abcdefghijklmnopqrstuvwxyz0123456789-_": slug += ch return slugn = int(input())used = set()for _ in range(n): line = input() level = len(line) - len(line.lstrip("#")) if not (1 <= level <= 6 and line[level:level + 1] == " "): continue # "#tag", "#######", 보통 문장은 제목이 아니다 title = line[level:].strip() base = make_slug(title) slug, k = base, 1 while slug in used: # 만든 것까지 포함해 안 쓴 이름이 될 때까지 slug = f"{base}-{k}" k += 1 used.add(slug) print(" " * (level - 1) + f"- [{title}](#{slug})")
JavaScriptmain.js · 15 lines
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const used = new Set();const out = [];for (const line of lines.slice(1, 1 + n)) { const m = /^(#{1,6}) (.*)$/.exec(line); // # 1~6개 다음에 빈칸이 있어야 제목 if (!m) continue; const title = m[2].trim(); const base = title.toLowerCase().replace(/ /g, "-").replace(/[^a-z0-9_-]/g, ""); let slug = base; for (let k = 1; used.has(slug); k++) slug = `${base}-${k}`; used.add(slug); out.push(" ".repeat(m[1].length - 1) + `- [${title}](#${slug})`);}console.log(out.join("\n"));
TypeScriptmain.ts · 16 lines
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]);const used = new Set<string>();const out: string[] = [];for (const line of lines.slice(1, 1 + n)) { const m: RegExpExecArray | null = /^(#{1,6}) (.*)$/.exec(line); // # 1~6개 다음에 빈칸이 있어야 제목 if (m === null) continue; const title: string = m[2].trim(); const base: string = title.toLowerCase().replace(/ /g, "-").replace(/[^a-z0-9_-]/g, ""); let slug: string = base; for (let k = 1; used.has(slug); k++) slug = `${base}-${k}`; used.add(slug); out.push(" ".repeat(m[1].length - 1) + `- [${title}](#${slug})`);}console.log(out.join("\n"));
JavaMain.java · 37 lines
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashSet;import java.util.Locale;import java.util.Set;public class Main { static String makeSlug(String title) { StringBuilder sb = new StringBuilder(); for (char ch : title.toLowerCase(Locale.ROOT).toCharArray()) { if (ch == ' ') sb.append('-'); else if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_') sb.append(ch); } return sb.toString(); } public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(in.readLine().trim()); Set<String> used = new HashSet<>(); StringBuilder out = new StringBuilder(); for (int i = 0; i < n; i++) { String line = in.readLine(); int level = 0; while (level < line.length() && line.charAt(level) == '#') level++; if (level < 1 || level > 6 || level >= line.length() || line.charAt(level) != ' ') continue; String title = line.substring(level).trim(); String base = makeSlug(title); String slug = base; for (int k = 1; used.contains(slug); k++) slug = base + "-" + k; used.add(slug); out.append(" ".repeat(level - 1)).append("- [").append(title).append("](#").append(slug).append(")\n"); } System.out.print(out); }}
C#Main.cs · 37 lines
using System;using System.Collections.Generic;using System.Text;class Program{ static string MakeSlug(string title) { var sb = new StringBuilder(); foreach (char ch in title.ToLowerInvariant()) { if (ch == ' ') sb.Append('-'); else if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_') sb.Append(ch); } return sb.ToString(); } static void Main() { int n = int.Parse(Console.ReadLine()); var used = new HashSet<string>(); var out_ = new StringBuilder(); for (int i = 0; i < n; i++) { string line = Console.ReadLine(); int level = 0; while (level < line.Length && line[level] == '#') level++; if (level < 1 || level > 6 || level >= line.Length || line[level] != ' ') continue; string title = line.Substring(level).Trim(); string slug = MakeSlug(title), baseSlug = slug; for (int k = 1; used.Contains(slug); k++) slug = baseSlug + "-" + k; used.Add(slug); out_.Append(new string(' ', 2 * (level - 1))).Append("- [").Append(title).Append("](#").Append(slug).Append(")\n"); } Console.Write(out_); }}
C++main.cpp · 32 lines
#include <iostream>#include <string>#include <unordered_set>#include <cctype>std::string makeSlug(const std::string& title) { std::string slug; for (char c : title) { char ch = (char)std::tolower((unsigned char)c); if (ch == ' ') slug += '-'; else if (std::isalnum((unsigned char)ch) || ch == '-' || ch == '_') slug += ch; } return slug;}int main() { std::string line; std::getline(std::cin, line); int n = std::stoi(line); std::unordered_set<std::string> used; for (int i = 0; i < n && std::getline(std::cin, line); i++) { size_t level = line.find_first_not_of('#'); if (level == std::string::npos || level < 1 || level > 6 || line[level] != ' ') continue; size_t first = line.find_first_not_of(' ', level); size_t last = line.find_last_not_of(' '); std::string title = line.substr(first, last - first + 1); std::string base = makeSlug(title), slug = base; for (int k = 1; used.count(slug); k++) slug = base + "-" + std::to_string(k); used.insert(slug); std::cout << std::string(2 * (level - 1), ' ') << "- [" << title << "](#" << slug << ")\n"; }}
Cmain.c · 46 lines
#include <stdio.h>#include <string.h>#include <ctype.h>static char used[200][160];static int usedCount = 0;static int isUsed(const char *s) { for (int i = 0; i < usedCount; i++) if (strcmp(used[i], s) == 0) return 1; return 0;}int main(void) { char line[128]; int n; if (!fgets(line, sizeof line, stdin)) return 0; sscanf(line, "%d", &n); for (int i = 0; i < n; i++) { if (!fgets(line, sizeof line, stdin)) break; line[strcspn(line, "\n")] = '\0'; int level = 0; while (line[level] == '#') level++; if (level < 1 || level > 6 || line[level] != ' ') continue; /* 앞뒤 빈칸을 좁혀 제목만 남긴다 */ char *start = line + level; while (*start == ' ') start++; char *end = start + strlen(start); while (end > start && end[-1] == ' ') end--; *end = '\0'; char base[128]; int b = 0; for (char *p = start; *p; p++) { char ch = (char)tolower((unsigned char)*p); if (ch == ' ') base[b++] = '-'; else if (isalnum((unsigned char)ch) || ch == '-' || ch == '_') base[b++] = ch; } base[b] = '\0'; char slug[160]; strcpy(slug, base); for (int k = 1; isUsed(slug); k++) sprintf(slug, "%s-%d", base, k); strcpy(used[usedCount++], slug); printf("%*s- [%s](#%s)\n", 2 * (level - 1), "", start, slug); } return 0;}
Craykonmain.cray · 45 lines
n = Console.ReadLine().ToNumber();used = Dict();for (i = 0; i < n; i++){ line = Console.ReadLine(); level = 0; while (level < line.Length && line[level] == "#") { level += 1; } // # 1~6개 다음에 빈칸이 있어야 제목이다 if (level >= 1 && level <= 6 && level < line.Length && line[level] == " ") { title = line.Substring(level).Trim(); base = makeSlug(title); slug = base; k = 1; while (used.Contains(slug)) { slug = base + "-" + k; k += 1; } used[slug] = true; Console.WriteLine(" ".Repeat(level - 1) + "- [" + title + "](#" + slug + ")"); }}// 소문자로 바꾸고, 빈칸은 -, 영어·숫자·-·_ 밖의 글자는 버린다makeSlug(title){ keep = "abcdefghijklmnopqrstuvwxyz0123456789-_"; slug = ""; foreach (ch in title.ToLower()) { if (ch == " ") { slug += "-"; } else if (keep.Contains(ch)) { slug += ch; } } return slug;}