{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 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이라, 포인터를 옮겨 가며 찾아요.
크레이콘 자리찾기에는 시작 자리를 줄 수 없어서 줄.부분문자열(위치).자리찾기("[[")로 잘라 찾은 뒤 위치를 더해요.
별표 n개는 Python "*" * n, JS·TS·Java "*".repeat(n), C# new string('*', n), C++ std::string(n, '*'), 크레이콘 "*".되풀이(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;    }    콘솔.쓰기(줄);    줄 = 콘솔.줄읽기();}콘솔.쓰기(구간수 + " " + 가린수);