첫 줄에 템플릿 문자열 T가 주어진다.
둘째 줄에 정의 수 n이 주어지고, 다음 n줄에 "이름=값"이 주어진다.
값은 그 줄의 첫 "=" 뒤에 있는 모든 글자이고, 비어 있을 수도 있다.
같은 이름이 여러 번 정의되면 마지막 정의만 쓴다.
T 안의 자리 표시 {이름}을 그 이름의 값으로 바꾼 문자열을 한 줄에 출력하라.
정의되지 않은 이름의 자리 표시는 {이름} 그대로 둔다.
바꿔 넣은 값은 다시 살피지 않는다. 값 안에 {이름}이나 $, \ 같은 글자가 있어도 그대로 출력한다.
입력
T는 1 ~ 1000글자이고 빈칸으로 시작하거나 끝나지 않는다. T의 { 와 } 는 자리 표시에만 쓰이며, 자리 표시는 { + 영어 소문자 1 ~ 10글자 + } 이다. 0 ≤ n ≤ 50. 이름은 영어 소문자 1 ~ 10글자. 값은 출력 가능한 ASCII 0 ~ 30글자({, }, =, $, \ 포함 가능)이고 빈칸으로 시작하거나 끝나지 않는다
출력
자리 표시를 바꾼 T 한 줄
예시 1 · stdin
Dear {name}, your code is {code}. {name}, see {link}.
3
name=Jun
code={name}$1
name=Mina
stdout
Dear Mina, your code is {name}$1. Mina, see {link}.
예시 2 · stdin
{first} and {second}
2
first={second}
second=B
stdout
{second} and B
main.py
오류 목록00F8 ↓ · ⇧F8 ↑
입력(stdin)
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
핵심은 템플릿을 왼쪽에서 오른쪽으로 한 번만 훑는 거예요. 이름마다 replace를 차례로 부르면 앞에서 넣은 값 속의 {second}가 뒤에서 또 바뀌어요(두 번째 예시). 바꾼 자리부터 다시 찾는 풀이도 같은 실수예요. 값이 자기 이름을 품으면(a=<{a}>) 끝나지도 않아요. 한 번에 훑는 방법은 두 가지예요. 하나는 정규식 \{([a-z]+)\}에 바꾸기 함수를 넘기는 거예요: Python re.sub(pat, fn, t), JS·TS t.replace(/…/g, fn), C# Regex.Replace(t, pat, m => …), Java matcher.replaceAll(r -> …). 다른 하나는 {를 찾고(find·indexOf·strchr) 그 앞까지 잘라 붙이며 나아가는 거예요. C·C++·크레이콘은 이 방법을 써요. 함정이 셋 더 있어요. JS t.replace("{a}", v)처럼 문자열을 넘기면 처음 하나만 바뀌어요. 모두 바꾸려면 replaceAll이나 /g 정규식을 써야 해요. JS에서 바꿀 값을 함수가 아니라 문자열로 넘기면 $&·$1·$$가 특별한 뜻이 되고, Python re.sub의 문자열 값도 \1을 그룹으로 읽어요. Java는 replaceAll에 함수를 넘겨도 돌려준 문자열의 $1·\를 읽어서 Matcher.quoteReplacement로 감싸야 해요. C# MatchEvaluator가 돌려준 값은 그대로 들어가요. 크레이콘은 rest.자리찾기("{")로 찾고 rest.부분문자열(0, open)로 잘라요. 자리찾기에는 시작 위치를 줄 수 없어서, 처리한 앞부분을 잘라 낸 rest에서 다시 찾아요.
Pythonmain.py · 17 줄
import retemplate = input()n = int(input())values = {}for _ in range(n): key, value = input().split("=", 1) values[key] = value # 같은 이름이 또 나오면 나중 값이 이긴다def fill(m): # 정의된 이름이면 값을, 아니면 {이름} 그대로 return values.get(m.group(1), m.group(0))# 바꾸기 함수를 넘기면 값 안의 {..}나 \1을 다시 읽지 않는다print(re.sub(r"\{([a-z]+)\}", fill, template))
JavaScriptmain.js · 11 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const template = lines[0];const n = Number(lines[1]);const values = new Map();for (let i = 2; i < 2 + n; i++) { const eq = lines[i].indexOf("="); values.set(lines[i].slice(0, eq), lines[i].slice(eq + 1));}// 함수를 넘기면 돌려준 문자열을 그대로 넣는다($&·$1을 읽지 않음)const out = template.replace(/\{([a-z]+)\}/g, (whole, key) => (values.has(key) ? values.get(key) : whole));console.log(out);
TypeScriptmain.ts · 12 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const template: string = lines[0];const n: number = Number(lines[1]);const values = new Map<string, string>();for (let i = 2; i < 2 + n; i++) { const eq: number = lines[i].indexOf("="); values.set(lines[i].slice(0, eq), lines[i].slice(eq + 1));}// 함수를 넘기면 돌려준 문자열을 그대로 넣는다($&·$1을 읽지 않음)const out: string = template.replace(/\{([a-z]+)\}/g, (whole: string, key: string): string => values.get(key) ?? whole);console.log(out);
JavaMain.java · 25 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashMap;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;public class Main { public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String template = in.readLine(); int n = Integer.parseInt(in.readLine().trim()); Map<String, String> values = new HashMap<>(); for (int i = 0; i < n; i++) { String line = in.readLine(); int eq = line.indexOf('='); values.put(line.substring(0, eq), line.substring(eq + 1)); } Matcher m = Pattern.compile("\\{([a-z]+)\\}").matcher(template); // 돌려준 문자열 안의 $와 \는 그룹 참조로 읽히니 quoteReplacement로 감싼다 String out = m.replaceAll(r -> Matcher.quoteReplacement(values.getOrDefault(r.group(1), r.group()))); System.out.println(out); }}
C#Main.cs · 23 줄
using System;using System.Collections.Generic;using System.Text.RegularExpressions;class Program{ static void Main() { string template = Console.ReadLine(); int n = int.Parse(Console.ReadLine()); var values = new Dictionary<string, string>(); for (int i = 0; i < n; i++) { string line = Console.ReadLine(); int eq = line.IndexOf('='); values[line.Substring(0, eq)] = line.Substring(eq + 1); } // MatchEvaluator가 돌려준 문자열은 그대로 들어간다($ 해석 없음) string output = Regex.Replace(template, @"\{([a-z]+)\}", m => values.TryGetValue(m.Groups[1].Value, out string v) ? v : m.Value); Console.WriteLine(output); }}
C++main.cpp · 27 줄
#include <iostream>#include <string>#include <unordered_map>int main() { std::string t, line; std::getline(std::cin, t); std::getline(std::cin, line); int n = std::stoi(line); std::unordered_map<std::string, std::string> values; for (int i = 0; i < n; i++) { std::getline(std::cin, line); auto eq = line.find('='); values[line.substr(0, eq)] = line.substr(eq + 1); } std::string out; std::size_t pos = 0; for (auto open = t.find('{'); open != std::string::npos; open = t.find('{', pos)) { auto close = t.find('}', open); out += t.substr(pos, open - pos); auto it = values.find(t.substr(open + 1, close - open - 1)); out += it != values.end() ? it->second : t.substr(open, close - open + 1); pos = close + 1; // 넣은 값은 다시 보지 않고, 템플릿의 그다음부터 찾는다 } out += t.substr(pos); std::cout << out << '\n';}
Cmain.c · 46 줄
#include <stdio.h>#include <string.h>static char keys[50][11], vals[50][32];static int count = 0;/* 이름이 정의돼 있으면 그 값, 없으면 NULL. 나중에 넣은 것이 이기도록 뒤에서부터 찾는다 */static const char *lookup(const char *key) { for (int i = count - 1; i >= 0; i--) if (strcmp(keys[i], key) == 0) return vals[i]; return NULL;}int main(void) { static char t[1100], line[64]; int n; fgets(t, sizeof t, stdin); t[strcspn(t, "\n")] = '\0'; fgets(line, sizeof line, stdin); sscanf(line, "%d", &n); for (int i = 0; i < n; i++) { fgets(line, sizeof line, stdin); line[strcspn(line, "\n")] = '\0'; char *eq = strchr(line, '='); *eq = '\0'; strcpy(keys[count], line); strcpy(vals[count], eq + 1); count++; } const char *p = t; const char *open; while ((open = strchr(p, '{')) != NULL) { const char *close = strchr(open, '}'); fwrite(p, 1, open - p, stdout); /* { 앞까지 그대로 */ char key[16]; int len = (int)(close - open - 1); memcpy(key, open + 1, len); key[len] = '\0'; const char *v = lookup(key); if (v) fputs(v, stdout); else fwrite(open, 1, close - open + 1, stdout); /* 없는 이름은 {이름} 그대로 */ p = close + 1; } puts(p); return 0;}
Craykonmain.cray · 31 줄
t = 콘솔.줄읽기();n = 콘솔.줄읽기().수로변환();values = 사전();반복 (i = 0; i < n; i++){ line = 콘솔.줄읽기(); eq = line.자리찾기("="); values[line.부분문자열(0, eq)] = line.부분문자열(eq + 1);}out = "";rest = t;open = rest.자리찾기("{");동안 (open != -1){ close = rest.자리찾기("}"); key = rest.부분문자열(open + 1, close - open - 1); out += rest.부분문자열(0, open); 만약 (values.있나(key)) { out += values[key]; } 아니면 { out += rest.부분문자열(open, close - open + 1); } // 넣은 값은 다시 보지 않고, 템플릿의 남은 부분에서만 찾는다 rest = rest.부분문자열(close + 1); open = rest.자리찾기("{");}out += rest;콘솔.쓰기(out);