{ }codelper
文字列の置換辞書の検索P0243

まとめて置き換える台本直し

演劇の台本で、いくつかの言葉を別の言葉に置き換えたい。
置き換え規則は "a b" の形で、台本の中の a を b に変えるという意味だ。
規則は一つずつ順に使うのではなく、すべてまとめて一度に使う。
だから "Tom Ann" と "Ann Tom" の二つの規則があれば、二つの名前が入れ替わる。
台本の各行は、先頭の文字から左から右へ見ながら、次のように置き換える。
今の位置から始まる a を持つ規則のうち、a がいちばん長い規則を一つ選ぶ。
そのような規則があれば、その規則の b を書き、a の文字数だけ先へ進む。
なければ、今の文字をそのまま書き、一文字先へ進む。
書き込んだ b は二度と調べない。
a が単語の一部であっても置き換える。
大文字と小文字は別の文字で、a と b の中の記号(.
* ( \ $ など)は特別な意味を持たず、その文字そのものだ。
一行目に規則の数 m、続く m 行に規則が一つずつ与えられる。
次の行に台本の行数 k、続く k 行に台本が一行ずつ与えられる。
置き換えた台本を入力の順に k 行で出力せよ。

入力
1 ≤ m ≤ 50。
a と b は空白を含まない ASCII 文字(コード 33~126)1~10 文字で、a と b の間に空白が一つある。
規則の a はすべて異なる(a と b は同じでもよい)。
1 ≤ k ≤ 20。
台本の各行は ASCII 文字(コード 32~126)1~1,000 文字で、空白で始まったり終わったりしない
出力
k 行
例 1 · stdin
3
Tom Ann
Ann Tom
To Mr.
2
Tom met Ann.
Ann told Tom: Today!
stdout
Ann met Tom.
Tom told Ann: Mr.day!
例 2 · stdin
2
a.b x
x a.b
1
a.b axb x xx
stdout
x aa.bb a.b a.ba.b
main.py
入力(stdin)

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

$ 準備完了

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

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

要は「一度の走査でまとめて」です。
各言語の置換関数(Python str.replace、JS・TS replaceAll、Java replace、C# Replace、Craykon 置き換え)は規則を一つしか扱わないので、規則ごとに順に呼ぶと Tom→Ann の後の Ann→Tom がいま変えた分まで戻してしまい、全部 Tom になります。
そこで位置ごとに「ここから始まるいちばん長い a」を辞書で探します。
a は 10 文字以下なので長さ 10 から 1 まで切り出して辞書にあるか聞けば十分です:Python dict、JS・TS Map.has、Java HashMap.containsKey、C# Dictionary.TryGetValue、C++ unordered_map::find、Craykon 辞書() と 含む、切り出しは 切り出す(i, len)。
C には辞書がないので 50 個の規則を strncmp(text + i, a, len) で一つずつ比べます。
Python なら re.escape した a を長い順に | でつないで re.sub すれば一度で済みます。
正規表現の | は左から試すので、短いほうが先だと To が Tom を横取りし、re.escape(JS では手で escape)を忘れると . が任意の文字に一致し、( は正規表現エラーになります。
Java の replaceAll も正規表現なので同じ問題があり、文字どおり置き換えるのは replace です。
ほかの落とし穴:先に書かれた規則を選ぶ(いちばん長いものでなければならない)、置き換えた後に一文字しか進まず a の残りをまた読む、結果をもう一度走査して b まで置き換える。

Pythonmain.py · 14 行
import reimport sys lines = sys.stdin.read().split("\n")m = int(lines[0])rules = dict(line.split(" ") for line in lines[1:1 + m])k = int(lines[1 + m]) # 긴 것부터 늘어놓은 a들을 | 로 잇는다. re.escape로 . * ( 같은 기호를 글자 그대로 찾게 한다.# 정규식의 | 는 왼쪽 것부터 맞춰 보므로, 긴 것이 앞에 있으면 그 자리에서 가장 긴 a가 걸린다.pattern = re.compile("|".join(re.escape(a) for a in sorted(rules, key=len, reverse=True)))for text in lines[2 + m:2 + m + k]:    # re.sub는 왼쪽부터 한 번 훑고, 바꿔 넣은 글자는 다시 보지 않는다    print(pattern.sub(lambda mt: rules[mt.group()], text))
JavaScriptmain.js · 28 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const m = Number(lines[0]);const rules = new Map();for (const line of lines.slice(1, 1 + m)) {  const [a, b] = line.split(" ");  rules.set(a, b);}const k = Number(lines[1 + m]); function rewrite(text) {  const out = [];  let i = 0;  while (i < text.length) {    let len = Math.min(10, text.length - i);    // 지금 자리에서 가장 긴 a부터 찾아본다    while (len > 0 && !rules.has(text.slice(i, i + len))) len--;    if (len > 0) {      out.push(rules.get(text.slice(i, i + len)));      i += len;    } else {      out.push(text[i]);      i++;    }  }  return out.join("");} console.log(lines.slice(2 + m, 2 + m + k).map(rewrite).join("\n"));
TypeScriptmain.ts · 31 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const m = Number(lines[0]);const rules = new Map<string, string>();for (const line of lines.slice(1, 1 + m)) {  const [a, b] = line.split(" ");  rules.set(a, b);}const k = Number(lines[1 + m]); function rewrite(text: string): string {  const out: string[] = [];  let i = 0;  while (i < text.length) {    let len = Math.min(10, text.length - i);    // 지금 자리에서 가장 긴 a부터 찾아본다    while (len > 0 && !rules.has(text.slice(i, i + len))) len--;    const b = rules.get(text.slice(i, i + len));    if (len > 0 && b !== undefined) {      out.push(b);      i += len;    } else {      out.push(text[i]);      i++;    }  }  return out.join("");} console.log(lines.slice(2 + m, 2 + m + k).map(rewrite).join("\n"));
JavaMain.java · 37 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.IOException;import java.util.HashMap;import java.util.Map; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int m = Integer.parseInt(in.readLine().trim());        Map<String, String> rules = new HashMap<>();        for (int r = 0; r < m; r++) {            String[] p = in.readLine().split(" ");            rules.put(p[0], p[1]);        }        int k = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int t = 0; t < k; t++) {            String text = in.readLine();            int i = 0;            while (i < text.length()) {                int len = Math.min(10, text.length() - i);                // 지금 자리에서 가장 긴 a부터 찾아본다                while (len > 0 && !rules.containsKey(text.substring(i, i + len))) len--;                if (len > 0) {                    out.append(rules.get(text.substring(i, i + len)));                    i += len;                } else {                    out.append(text.charAt(i));                    i++;                }            }            out.append('\n');        }        System.out.print(out);    }}
C#Main.cs · 43 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int m = int.Parse(Console.ReadLine().Trim());        var rules = new Dictionary<string, string>();        for (int r = 0; r < m; r++)        {            string[] p = Console.ReadLine().Split(' ');            rules[p[0]] = p[1];        }        int k = int.Parse(Console.ReadLine().Trim());        var out_ = new StringBuilder();        for (int t = 0; t < k; t++)        {            string text = Console.ReadLine();            int i = 0;            while (i < text.Length)            {                int len = Math.Min(10, text.Length - i);                string b = null;                // 지금 자리에서 가장 긴 a부터 찾아본다                while (len > 0 && !rules.TryGetValue(text.Substring(i, len), out b)) len--;                if (len > 0)                {                    out_.Append(b);                    i += len;                }                else                {                    out_.Append(text[i]);                    i++;                }            }            out_.Append('\n');        }        Console.Write(out_);    }}
C++main.cpp · 42 行
#include <algorithm>#include <iostream>#include <string>#include <unordered_map> int main() {    std::ios::sync_with_stdio(false);    int m, k;    std::cin >> m;    std::unordered_map<std::string, std::string> rules;    for (int r = 0; r < m; r++) {        std::string a, b;        std::cin >> a >> b;        rules[a] = b;    }    std::cin >> k;    std::string text;    std::getline(std::cin, text); // 수 뒤에 남은 줄 끝을 버린다    std::string out;    for (int t = 0; t < k; t++) {        std::getline(std::cin, text);        size_t i = 0;        while (i < text.size()) {            size_t len = std::min<size_t>(10, text.size() - i);            // 지금 자리에서 가장 긴 a부터 찾아본다            auto it = rules.end();            for (; len > 0; len--) {                it = rules.find(text.substr(i, len));                if (it != rules.end()) break;            }            if (len > 0) {                out += it->second;                i += len;            } else {                out += text[i];                i++;            }        }        out += '\n';    }    std::cout << out;}
Cmain.c · 37 行
#include <stdio.h>#include <string.h> static char from[50][11], to[50][11];static int flen[50];static char text[1002]; int main(void) {    int m, k;    scanf("%d", &m);    for (int r = 0; r < m; r++) {        scanf("%10s %10s", from[r], to[r]);        flen[r] = (int)strlen(from[r]);    }    scanf("%d ", &k); /* 수 뒤의 줄바꿈까지 건너뛴다(대본은 빈칸으로 시작하지 않는다) */    for (int t = 0; t < k; t++) {        if (!fgets(text, sizeof text, stdin)) break;        text[strcspn(text, "\r\n")] = '\0';        int n = (int)strlen(text);        for (int i = 0; i < n;) {            int best = -1;            /* 이 자리에서 시작하는 a 가운데 가장 긴 것 */            for (int r = 0; r < m; r++)                if (flen[r] <= n - i && (best < 0 || flen[r] > flen[best]) && strncmp(text + i, from[r], flen[r]) == 0)                    best = r;            if (best >= 0) {                fputs(to[best], stdout);                i += flen[best];            } else {                putchar(text[i]);                i++;            }        }        putchar('\n');    }    return 0;}
Craykonmain.cray · 34 行
m = コンソール.行読み().数に();rules = 辞書();繰り返す (r = 0; r < m; r++){    p = コンソール.行読み().分ける(" ");    rules[p[0]] = p[1];}k = コンソール.行読み().数に();繰り返す (t = 0; t < k; t++){    text = コンソール.行読み();    out = [];    i = 0;    間 (i < text.長さ)    {        len = 数学.小さい方(10, text.長さ - i);        // 지금 자리에서 가장 긴 a부터 찾아본다        間 (len > 0 && !rules.含む(text.切り出す(i, len)))        {            len--;        }        もし (len > 0)        {            out.追加(rules[text.切り出す(i, len)]);            i += len;        }        でなければ        {            out.追加(text[i]);            i++;        }    }    コンソール.書く(out.つなぐ(""));}