{ }codelper
文字列の検索文字列の置換部分文字列P0101

穴埋めテンプレート

一行目にテンプレート文字列 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
入力(stdin)

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

$ 準備完了

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

ほかの言語では?言語別の解答を見る · 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++・Craykon はこちらを使います。
落とし穴がさらに三つあります。
JS で t.replace("{a}", v) のように文字列を渡すと最初の一つしか置き換わりません。
すべて置き換えるには replaceAll か /g の正規表現を使います。
JS で置き換える値を関数ではなく文字列で渡すと $&・$1・$$ が特別な意味になり、Python re.sub の文字列の値も \1 をグループとして読みます。
Java は replaceAll に関数を渡しても、返した文字列の $1・\ を読むので Matcher.quoteReplacement で包みます。
C# の MatchEvaluator が返した値はそのまま入ります。
Craykon は rest.位置検索("{") で探し、rest.切り出す(0, open) で切ります。
位置検索 には開始位置を渡せないので、処理した前の部分を切り落とした rest でもう一度探します。

Pythonmain.py · 17 行
import re template = 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);