{ }codelper
大文字・小文字空白の除去文字列の連結P0145

例外の単語があるタイトルケース

一行目に例外の単語の数 k が与えられ、二行目に例外の単語 k 個が空白一つずつで区切られて与えられる(k が 0 なら空行である)。
三行目にタイトルの数 n が与えられ、続いて英語のタイトルが一行に一つずつ n 個与えられる。
タイトルごとにタイトルケースに直し、一行に一つずつ出力せよ。
タイトルの行は空白で単語に分ける。
行の前後の空白は捨て、単語の間の空白は何個でも一つにまとめる。
単語の末尾に : が付いていれば、その後ろからが副題である。
: もその単語の一部なので、"of:" は例外の単語 "of" と同じではない。
単語ごとに、まず英大文字をすべて小文字に変える。
その単語が例外の単語のどれかと同じで、最初の単語でも、最後の単語でも、: で終わる単語のすぐ次の単語(副題の最初の単語)でもなければ、すべて小文字のままにする。
それ以外の単語は先頭の一文字だけを大文字に変える。
先頭が数字ならそのままにする。
' や - の後の文字は新しい単語の始まりではないので小文字のままにする("don't" → "Don't"、"e-mail" → "E-mail"、"3D" → "3d")。
直した単語を空白一つずつでつないで出力する。

入力
0 ≤ k ≤ 16。
例外の単語は互いに異なり、英小文字 1〜10 文字である。
1 ≤ n ≤ 100。
タイトルの行は長さ 1〜200 で、英大文字・小文字、数字、'、-、:、空白だけからなり、単語が少なくとも一つある。
単語は英字か数字で始まり、: は単語の末尾にだけ現れる
出力
直したタイトル n 行
例 1 · stdin
5
a of the and in
4
  the lord OF the rings  
war   and peace
don't look 3d e-mail
the
stdout
The Lord of the Rings
War and Peace
Don't Look 3d E-mail
The
例 2 · stdin
2
of the
3
OF THE
THE END OF THE WORLD
the hobbit: the desolation of smaug
stdout
Of The
The End of the World
The Hobbit: The Desolation of Smaug
main.py
入力(stdin)

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

$ 準備完了

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

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

落とし穴は五つです。
一つ目、Python str.title() は文字でないもののすぐ後をすべて新しい単語とみなし、"Don'T"、"E-Mail"、"3D" を作ります。
単語ごとに low[0].upper() + low[1:] のように最初の文字だけ変える必要があります(capitalize() もこうします)。
大文字・小文字の変換は JS・TS toUpperCase・toLowerCase、Java toUpperCase(Locale.ROOT)、C# ToUpperInvariant・char.ToUpperInvariant、C・C++ は一文字ずつ toupper・tolower、Craykon 大文字()・小文字() です。
二つ目、例外の単語かどうかは小文字にしてから聞かないと "OF" が引っかかりません。
三つ目、最初の単語だけでなく最後の単語も例外から外れます。"OF THE" は "Of The" です。
四つ目、: の後の副題の最初の単語も例外から外れます。"the hobbit: the desolation" の二つ目の "the" は "The" です。
前の単語が : で終わるかは Python endswith(":")、JS・TS・Java endsWith(":")、C# EndsWith(':')、C w[strlen(w) - 1] == ':'、C++ w.back() == ':'、Craykon で終わる(":") で聞きます。
五つ目、空白の整えと分割です。
Python の引数なし split()、C# Split(' ', StringSplitOptions.RemoveEmptyEntries)、C strtok、C++ >>、Craykon の区切りなし 分ける() は前後・複数の空白をすべて飛ばします。
JS・TS・Java の split(" ") は空白二つの間と先頭に空文字列を残すので、trim() の後に正規表現 / +/・" +" で分けます。
つなぐのは Python " ".join(out)、JS・TS out.join(" ")、Java String.join(" ", words)、C# string.Join(" ", words)、Craykon out.つなぐ(" ") で、C・C++ は最初の単語の前だけ空白を抜いて書きます。

Pythonmain.py · 14 行
k = int(input())small = set(input().split())n = int(input())for _ in range(n):    words = input().split()  # 인자 없는 split()은 앞뒤·여러 칸 공백을 모두 없앤다    out = []    for i, w in enumerate(words):        low = w.lower()        # 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다        if 0 < i < len(words) - 1 and not words[i - 1].endswith(":") and low in small:            out.append(low)        else:            out.append(low[0].upper() + low[1:])  # title()은 ' 와 - 뒤도 대문자로 만든다    print(" ".join(out))
JavaScriptmain.js · 17 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const k = Number(lines[0]);const small = new Set(lines[1].split(" ").filter((w) => w !== ""));const n = Number(lines[2]);const out = [];for (const line of lines.slice(3, 3 + n)) {  // trim 뒤에 공백 덩어리로 나눈다: split(" ")은 빈 칸마다 빈 문자열을 남긴다  const words = line.trim().split(/ +/);  const fixed = words.map((w, i) => {    const low = w.toLowerCase();    // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다    if (i > 0 && i < words.length - 1 && !words[i - 1].endsWith(":") && small.has(low)) return low;    return low[0].toUpperCase() + low.slice(1);  });  out.push(fixed.join(" "));}console.log(out.join("\n"));
TypeScriptmain.ts · 18 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const k: number = Number(lines[0]);const small = new Set<string>(lines[1].split(" ").filter((w) => w !== ""));const n: number = Number(lines[2]);const out: string[] = [];for (const line of lines.slice(3, 3 + n)) {  // trim 뒤에 공백 덩어리로 나눈다: split(" ")은 빈 칸마다 빈 문자열을 남긴다  const words: string[] = line.trim().split(/ +/);  const fixed: string[] = words.map((w: string, i: number): string => {    const low: string = w.toLowerCase();    // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다    if (i > 0 && i < words.length - 1 && !words[i - 1].endsWith(":") && small.has(low)) return low;    return low[0].toUpperCase() + low.slice(1);  });  out.push(fixed.join(" "));}console.log(out.join("\n"));
JavaMain.java · 30 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.Arrays;import java.util.HashSet;import java.util.Locale;import java.util.Set; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        in.readLine();        Set<String> small = new HashSet<>(Arrays.asList(in.readLine().trim().split(" ")));        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int t = 0; t < n; t++) {            // trim 뒤 정규식 " +"로 나눈다: split(" ")은 빈칸 두 개 사이에 빈 문자열을 남긴다            String[] words = in.readLine().trim().split(" +");            for (int i = 0; i < words.length; i++) {                String low = words[i].toLowerCase(Locale.ROOT);                // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다                boolean inner = i > 0 && i < words.length - 1 && !words[i - 1].endsWith(":");                if (inner && small.contains(low)) words[i] = low;                else words[i] = low.substring(0, 1).toUpperCase(Locale.ROOT) + low.substring(1);            }            out.append(String.join(" ", words)).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 26 行
using System;using System.Collections.Generic; class Program{    static void Main()    {        Console.ReadLine();        var small = new HashSet<string>(Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries));        int n = int.Parse(Console.ReadLine());        for (int t = 0; t < n; t++)        {            // RemoveEmptyEntries가 앞뒤·여러 칸 공백에서 생기는 빈 조각을 버린다            string[] words = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            for (int i = 0; i < words.Length; i++)            {                string low = words[i].ToLowerInvariant();                // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다                bool inner = i > 0 && i < words.Length - 1 && !words[i - 1].EndsWith(':');                if (inner && small.Contains(low)) words[i] = low;                else words[i] = char.ToUpperInvariant(low[0]) + low.Substring(1);            }            Console.WriteLine(string.Join(" ", words));        }    }}
C++main.cpp · 35 行
#include <cctype>#include <iostream>#include <sstream>#include <string>#include <unordered_set>#include <vector> int main() {    int k, n;    std::cin >> k;    std::unordered_set<std::string> small;    for (int i = 0; i < k; i++) {        std::string w;        std::cin >> w;        small.insert(w);    }    std::cin >> n;    std::string line;    std::getline(std::cin, line); // n 뒤의 줄 끝    for (int t = 0; t < n && std::getline(std::cin, line); t++) {        // >> 는 앞뒤·여러 칸 빈칸을 건너뛰고 낱말만 읽는다        std::istringstream ss(line);        std::vector<std::string> words;        for (std::string w; ss >> w;) words.push_back(w);        for (size_t i = 0; i < words.size(); i++) {            std::string& w = words[i];            for (char& c : w) c = (char)std::tolower((unsigned char)c);            // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다            bool inner = i > 0 && i + 1 < words.size() && words[i - 1].back() != ':';            if (!(inner && small.count(w))) w[0] = (char)std::toupper((unsigned char)w[0]);            std::cout << (i ? " " : "") << w;        }        std::cout << '\n';    }}
Cmain.c · 39 行
#include <stdio.h>#include <string.h>#include <ctype.h> static char small[20][16];static int k; static int isSmall(const char *w) {    for (int i = 0; i < k; i++)        if (strcmp(small[i], w) == 0) return 1;    return 0;} int main(void) {    int n;    char line[256];    scanf("%d", &k);    for (int i = 0; i < k; i++) scanf("%15s", small[i]);    scanf("%d", &n);    fgets(line, sizeof line, stdin); /* n 뒤의 줄 끝 */    for (int t = 0; t < n; t++) {        if (!fgets(line, sizeof line, stdin)) break;        line[strcspn(line, "\n")] = '\0';        /* strtok은 앞뒤·여러 칸 빈칸을 건너뛰고 낱말만 준다 */        char *words[128];        int m = 0;        for (char *w = strtok(line, " "); w; w = strtok(NULL, " ")) words[m++] = w;        for (int i = 0; i < m; i++) {            for (char *p = words[i]; *p; p++) *p = (char)tolower((unsigned char)*p);            /* 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다 */            int inner = i > 0 && i < m - 1 && words[i - 1][strlen(words[i - 1]) - 1] != ':';            if (!(inner && isSmall(words[i])))                words[i][0] = (char)toupper((unsigned char)words[i][0]);            printf(i == 0 ? "%s" : " %s", words[i]);        }        putchar('\n');    }    return 0;}
Craykonmain.cray · 27 行
k = コンソール.行読み().数に();small = 辞書();それぞれ (w の中 コンソール.行読み().分ける()){    small[w] = 真;}n = コンソール.行読み().数に();繰り返す (t = 0; t < n; t++){    // 기준 없는 나누기()는 앞뒤·여러 칸 빈칸을 모두 건너뛴다    words = コンソール.行読み().空白除去().分ける();    out = [];    繰り返す (i = 0; i < words.個数; i++)    {        low = words[i].小文字();        // 첫 낱말, 마지막 낱말, : 뒤의 부제 첫 낱말은 예외 낱말이어도 대문자로 시작한다        もし (i > 0 && i < words.個数 - 1 && !words[i - 1].で終わる(":") && small.含む(low))        {            out.追加(low);        }        でなければ        {            out.追加(low[0].大文字() + low.切り出す(1));        }    }    コンソール.書く(out.つなぐ(" "));}