{ }codelper
空白の除去大文字・小文字集合P0141

Markdown の目次を作る

一行目に行数 n が与えられ、続いて Markdown 文書の n 行が与えられる。
行の先頭に # が 1〜6 個あり、そのすぐ次の文字が空白なら、その行は見出しである。
# の個数が見出しの段階である。
"#tag" のように # の後に空白がない行、# が 7 個以上の行、先頭が空白の行、"#" だけの行は見出しではない。
見出しの文は、# の後の残りから前後の空白をすべて取り除いたものである(内側の空白はそのまま残す)。
見出しごとにスラッグ(リンク名)を作る。
見出しの文の英大文字を小文字に変え、空白一つごとに - 一つに変え、英小文字・数字・-・_ 以外の文字は消す。
こうして作った名前を元の名前と呼ぶ。
前の見出しがすでに使ったスラッグと同じなら、元の名前の後に -1、-2、-3 … を順に付けてみて、まだ誰も使っていない最初の名前をスラッグにする。
使ったスラッグには、こうして番号を付けて作ったものも含まれる。
見出しが出た順に目次を一行ずつ出力せよ。
目次の一行は、空白 2×(段階−1) 個と "- [見出しの文](#スラッグ)" である。

入力
1 ≤ n ≤ 200。
各行の長さは 0〜100 で、ASCII 文字(コード 32〜126)だけを使う。
角かっこ [ ] と丸かっこ ( ) は出てこない。
見出しは少なくとも一つあり、見出し行の # の後には英字か数字が少なくとも一つある
出力
見出しの数だけの行。
各行は字下げと "- [見出しの文](#スラッグ)"
例 1 · stdin
6
# Hello World  
#tag
## Hello World
### C# & Rust!
## hello-world
# Hello World
stdout
- [Hello World](#hello-world)
  - [Hello World](#hello-world-1)
    - [C# & Rust!](#c--rust)
  - [hello-world](#hello-world-2)
- [Hello World](#hello-world-3)
例 2 · stdin
3
# a
# a
# a-1
stdout
- [a](#a)
- [a](#a-1)
- [a-1](#a-1-1)
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

落とし穴は四つです。
一つ目、前後の空白を整えないと "# Hello World " の行末の空白がスラッグ末尾の -- になります。
整えるのは Python strip()、JS・TS・Java trim()、C# Trim()、Craykon 空白除去() で、C は両端のポインタを狭め、C++ は find_first_not_of・find_last_not_of で切ります。
二つ目、同じ元の名前の個数だけを数えると(二つ目の "a" が a-1)、"a"、"a"、"a-1" で a-1 が二回出ます。
これまで使ったスラッグをすべて集合に入れ、空いている名前が出るまで番号を上げる必要があります。
集合は Python set、JS・TS Set、Java・C# HashSet、C++ unordered_set で、C にはないので配列を先頭から調べます(200 個なので十分速いです)。
Craykon は 辞書() に used[slug] = 真 で入れ、used.含む(slug) で聞きます。
三つ目、# の後の空白を確かめないと "#tag" まで見出しになります。
JS の正規表現 /^(#{1,6}) / は、# が 7 個だと空白の位置に # が来るので自然に合いません。
四つ目、小文字にする前に a〜z だけ残すと大文字が消えます。
小文字にするのは Python lower()、JS・TS toLowerCase()、Java toLowerCase(Locale.ROOT)(トルコ語設定で I が点のない ı にならないように)、C# ToLowerInvariant()、C・C++ は一文字ずつ tolower、Craykon 小文字() です。

Pythonmain.py · 25 行
def make_slug(title):    slug = ""    for ch in title.lower():        if ch == " ":            slug += "-"        elif ch in "abcdefghijklmnopqrstuvwxyz0123456789-_":            slug += ch    return slug  n = 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 行
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 行
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 行
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 行
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 行
#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 行
#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 行
n = コンソール.行読み().数に();used = 辞書();繰り返す (i = 0; i < n; i++){    line = コンソール.行読み();    level = 0;    間 (level < line.長さ && line[level] == "#")    {        level += 1;    }    // # 1~6개 다음에 빈칸이 있어야 제목이다    もし (level >= 1 && level <= 6 && level < line.長さ && line[level] == " ")    {        title = line.切り出す(level).空白除去();        base = makeSlug(title);        slug = base;        k = 1;        間 (used.含む(slug))        {            slug = base + "-" + k;            k += 1;        }        used[slug] = 真;        コンソール.書く("  ".繰り返し(level - 1) + "- [" + title + "](#" + slug + ")");    }} // 소문자로 바꾸고, 빈칸은 -, 영어·숫자·-·_ 밖의 글자는 버린다makeSlug(title){    keep = "abcdefghijklmnopqrstuvwxyz0123456789-_";    slug = "";    それぞれ (ch の中 title.小文字())    {        もし (ch == " ")        {            slug += "-";        }        でなければ もし (keep.含む(ch))        {            slug += ch;        }    }    返す slug;}