{ }codelper
文字列の検索文字列の繰り返し番兵ループP0171

ログの秘密の値を隠す

サーバーのログには、パスワードのような秘密の値が [[ と ]] の間に書かれている。
その間の文字をアスタリスク(*)で隠して、ログをもう一度出力したい。
ログは複数行で、内容がちょうど END の行が終わりの印である。
END の行とそのあとの行は処理も出力もしない。
一行はこのように処理する。
まず探し始める位置を行の先頭にする。
探し始める位置から最初に出てくる [[ を探す。
なければその行の処理は終わる。
その [[ のすぐ後ろから最初に出てくる ]] を探す。
なければその行の処理は終わる(閉じていない [[ のあとはそのまま残す)。
見つけた [[ と ]] の間の文字をすべて * に変える。
文字数はそのままで、[[ と ]] 自体は残す。
間に文字がなくても([[]])隠した区間一つと数える。
その ]] のすぐ後ろを新しい探し始める位置にして、また [[ を探す。
処理した行を入力の順に出力したあと、最後の行に隠した区間の数と * に変えた文字の数を空白一つで区切って出力する。

入力
END の行の前のログの行は 0 ~ 100 行。
ログの行はそれぞれ長さ 1 ~ 200 で、ASCII 文字(コード 32~126)だけを使い、先頭と末尾は空白ではない。
END の行は必ずある
出力
ログの行の数だけの行と、「区間の数 文字の数」の一行
例 1 · stdin
login user=kim pass=[[hunter2]] ok
token [[]] and [[abc
END
stdout
login user=kim pass=[[*******]] ok
token [[]] and [[abc
2 7
例 2 · stdin
]] [[x]] [[a[[b]]c]]
ENDING [[ok]]
END
after [[no]]
stdout
]] [[*]] [[****]]c]]
ENDING [[**]]
3 7
main.py
入力(stdin)

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

$ 準備完了

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

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

要点は「どこから探すか」です。
]] は行の先頭からではなく、いま見つけた [[ のすぐ後ろから探します。
そうしないと二つ目の例の一行目のように ]] が [[ より前にあるとき、違う組が取れてしまいます。
次の [[ も ]] のすぐ後ろから探せば、"[[a[[b]]c]]" の内側の [[ を新しい区間として数えません。
開始位置を渡す検索は Python s.find(t, pos)、JS・TS・Java s.indexOf(t, pos)、C# s.IndexOf(t, pos, StringComparison.Ordinal)、C++ s.find(t, pos) です。
見つからないとき Python・JS・TS・Java・C# は -1、C++ は std::string::npos です。
C# の IndexOf に文字列を渡すと既定ではカルチャに依存する比較になるので、Ordinal を付けるのが安全です。
C の strstr(p, t) は位置ではなくポインタを返し、なければ NULL なので、ポインタを進めながら探します。
Craykon の 位置検索(자리찾기)には開始位置を渡せないので、行.切り出す(位置).位置検索("[[") のように切ってから探し、位置 を足し戻します。
アスタリスク n 個は Python "*" * n、JS・TS・Java "*".repeat(n)、C# new string('*', n)、C++ std::string(n, '*')、Craykon "*".繰り返し(n) です。
C は文字数が変わらないのでその場で書き換えます。
終わりの印は行全体が END のときだけです。
startsWith("END") で聞くと ENDING の行で止まり、入力の終わりまで読むと END のあとの行まで数えてしまいます。
Java の line == "END" は同じオブジェクトかどうかを聞くので、読み込んだ行とは常に違うと出ます。
line.equals("END") で比べます。
閉じていない [[ のあとを最後まで隠したり、空の [[]] を数えなかったりするのもよくある間違いです。

Pythonmain.py · 25 行
import sys sections = 0masked = 0out = []line = sys.stdin.readline().rstrip("\n")# END 줄이 끝 표시다. 그 뒤의 줄은 읽지 않는다while line != "END":    pos = 0    while True:        i = line.find("[[", pos)        if i < 0:            break        j = line.find("]]", i + 2)  # 여는 [[ 바로 뒤부터 찾는다        if j < 0:            break  # 닫히지 않은 [[ 뒤는 그대로 둔다        n = j - (i + 2)        line = line[: i + 2] + "*" * n + line[j:]        sections += 1        masked += n        pos = j + 2    out.append(line)    line = sys.stdin.readline().rstrip("\n")out.append(f"{sections} {masked}")print("\n".join(out))
JavaScriptmain.js · 23 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");let sections = 0;let masked = 0;const out = [];// END 줄이 끝 표시다. 그 뒤의 줄은 보지 않는다for (let k = 0; lines[k] !== "END"; k++) {  let line = lines[k];  let pos = 0;  while (true) {    const i = line.indexOf("[[", pos);    if (i < 0) break;    const j = line.indexOf("]]", i + 2); // 여는 [[ 바로 뒤부터 찾는다    if (j < 0) break; // 닫히지 않은 [[ 뒤는 그대로 둔다    const n = j - (i + 2);    line = line.slice(0, i + 2) + "*".repeat(n) + line.slice(j);    sections++;    masked += n;    pos = j + 2;  }  out.push(line);}out.push(`${sections} ${masked}`);console.log(out.join("\n"));
TypeScriptmain.ts · 24 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");let sections = 0;let masked = 0;const out: string[] = [];// END 줄이 끝 표시다. 그 뒤의 줄은 보지 않는다for (let k = 0; lines[k] !== "END"; k++) {  let line: string = lines[k];  let pos = 0;  while (true) {    const i: number = line.indexOf("[[", pos);    if (i < 0) break;    const j: number = line.indexOf("]]", i + 2); // 여는 [[ 바로 뒤부터 찾는다    if (j < 0) break; // 닫히지 않은 [[ 뒤는 그대로 둔다    const n: number = j - (i + 2);    line = line.slice(0, i + 2) + "*".repeat(n) + line.slice(j);    sections++;    masked += n;    pos = j + 2;  }  out.push(line);}out.push(`${sections} ${masked}`);console.log(out.join("\n"));
JavaMain.java · 31 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int sections = 0, masked = 0;        StringBuilder out = new StringBuilder();        String line = br.readLine();        // END 줄이 끝 표시다. 문자열 비교는 equals로 한다        while (!line.equals("END")) {            int pos = 0;            while (true) {                int i = line.indexOf("[[", pos);                if (i < 0) break;                int j = line.indexOf("]]", i + 2); // 여는 [[ 바로 뒤부터 찾는다                if (j < 0) break; // 닫히지 않은 [[ 뒤는 그대로 둔다                int n = j - (i + 2);                line = line.substring(0, i + 2) + "*".repeat(n) + line.substring(j);                sections++;                masked += n;                pos = j + 2;            }            out.append(line).append('\n');            line = br.readLine();        }        out.append(sections).append(' ').append(masked).append('\n');        System.out.print(out);    }}
C#Main.cs · 34 行
using System;using System.Text; class Program{    static void Main()    {        int sections = 0, masked = 0;        var sb = new StringBuilder();        string line = Console.ReadLine();        // END 줄이 끝 표시다. 그 뒤의 줄은 읽지 않는다        while (line != "END")        {            int pos = 0;            while (true)            {                // string으로 찾으면 문화권 비교가 되므로 Ordinal을 준다                int i = line.IndexOf("[[", pos, StringComparison.Ordinal);                if (i < 0) break;                int j = line.IndexOf("]]", i + 2, StringComparison.Ordinal); // 여는 [[ 바로 뒤부터                if (j < 0) break; // 닫히지 않은 [[ 뒤는 그대로 둔다                int n = j - (i + 2);                line = line.Substring(0, i + 2) + new string('*', n) + line.Substring(j);                sections++;                masked += n;                pos = j + 2;            }            sb.Append(line).Append('\n');            line = Console.ReadLine();        }        sb.Append(sections).Append(' ').Append(masked).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 24 行
#include <iostream>#include <string> int main() {    std::string line;    int sections = 0, masked = 0;    // END 줄이 끝 표시다. 그 뒤의 줄은 읽지 않는다    while (std::getline(std::cin, line) && line != "END") {        std::size_t pos = 0;        while (true) {            std::size_t i = line.find("[[", pos);            if (i == std::string::npos) break;            std::size_t j = line.find("]]", i + 2); // 여는 [[ 바로 뒤부터 찾는다            if (j == std::string::npos) break; // 닫히지 않은 [[ 뒤는 그대로 둔다            std::size_t n = j - (i + 2);            line.replace(i + 2, n, std::string(n, '*'));            sections++;            masked += static_cast<int>(n);            pos = j + 2;        }        std::cout << line << '\n';    }    std::cout << sections << ' ' << masked << '\n';}
Cmain.c · 27 行
#include <stdio.h>#include <string.h> int main(void) {    char line[256];    int sections = 0, masked = 0;    /* END 줄이 끝 표시다. 그 뒤의 줄은 읽지 않는다 */    while (fgets(line, sizeof line, stdin)) {        line[strcspn(line, "\r\n")] = '\0';        if (strcmp(line, "END") == 0) break;        char *pos = line;        while (1) {            char *open = strstr(pos, "[[");            if (open == NULL) break;            char *close = strstr(open + 2, "]]"); /* 여는 [[ 바로 뒤부터 찾는다 */            if (close == NULL) break; /* 닫히지 않은 [[ 뒤는 그대로 둔다 */            /* 글자 수가 같으니 제자리에서 바꾼다 */            for (char *p = open + 2; p < close; p++) *p = '*';            sections++;            masked += (int)(close - (open + 2));            pos = close + 2;        }        puts(line);    }    printf("%d %d\n", sections, masked);    return 0;}
Craykonmain.cray · 32 行
구간수 = 0;가린수 = 0;줄 = コンソール.行読み();// END 줄이 끝 표시다. 그 뒤의 줄은 읽지 않는다間 (줄 != "END"){    위치 = 0;    間 (真)    {        // 자리찾기에는 시작 자리를 줄 수 없어서, 잘라 낸 뒤 찾고 떼어 낸 만큼 더한다        i = 줄.切り出す(위치).位置検索("[[");        もし (i < 0)        {            抜ける;        }        i += 위치;        j = 줄.切り出す(i + 2).位置検索("]]"); // 여는 [[ 바로 뒤부터 찾는다        もし (j < 0)        {            抜ける; // 닫히지 않은 [[ 뒤는 그대로 둔다        }        j += i + 2;        n = j - (i + 2);        줄 = 줄.切り出す(0, i + 2) + "*".繰り返し(n) + 줄.切り出す(j);        구간수 += 1;        가린수 += n;        위치 = j + 2;    }    コンソール.書く(줄);    줄 = コンソール.行読み();}コンソール.書く(구간수 + " " + 가린수);