{ }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" 判断的是不是同一个对象,对读进来的行总是 false,所以要用 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;    }    控制台.输出(줄);    줄 = 控制台.读行();}控制台.输出(구간수 + " " + 가린수);