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

$ 준비됐어요

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

다른 언어로는?언어별 풀이 보기 · 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, 크레이콘 대문자로변환()·소문자로변환()이에요.
둘째, 예외 낱말인지는 소문자로 바꾼 뒤에 물어야 "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() == ':', 크레이콘 끝나나(":")로 물어요.
다섯째, 공백 다듬기와 나누기예요.
Python 인자 없는 split(), C# Split(' ', StringSplitOptions.RemoveEmptyEntries), C strtok, C++ >>, 크레이콘 기준 없는 나누기()는 앞뒤·여러 칸 빈칸을 모두 건너뛰어요.
JS·TS·Java의 split(" ")은 빈칸 두 개 사이와 앞쪽에 빈 문자열을 남겨서, trim() 뒤에 정규식 / +/·" +"로 나눠야 해요.
잇기는 Python " ".join(out), JS·TS out.join(" "), Java String.join(" ", words), C# string.Join(" ", words), 크레이콘 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.잇기(" "));}