{ }codelper
공백 다듬기문자열 찾기문자열 자르기P0393

설정 파일의 값 다듬기

설정 파일의 줄을 읽어 이름과 값을 다듬는다. 첫 줄에 줄 수 n이 주어지고, 이어서 n개의 줄이 주어진다. 각 줄을 다음 순서로 처리한다. 먼저 줄의 양 끝에서 빈칸과 탭을 모두 뗀다. 그 결과가 빈 문자열이거나 '#'으로 시작하면 설정이 아니므로 건너뛴다. 나머지 줄은 처음 나오는 '='에서 둘로 나눈다. '='의 왼쪽에서 양 끝의 빈칸과 탭을 뗀 것이 이름이다. '='의 오른쪽은 값이고, 그 안에 '='이나 '#'이 더 있어도 값의 일부다. 값은 양 끝의 빈칸과 탭을 뗀 다음, 끝에서부터 빈칸, 탭, 쉼표(','), 세미콜론(';')이 이어지는 동안 이 네 글자를 모두 뗀다. 쉼표와 세미콜론은 이렇게 끝에서만 떼고 값의 앞쪽에서는 떼지 않지만, 남은 글자가 모두 이 네 글자이면 전부 떼어져 값이 빈 문자열이 된다. 그런 다음 값의 길이가 2 이상이고 첫 글자와 끝 글자가 같은 따옴표이면(둘 다 큰따옴표이거나 둘 다 작은따옴표) 그 한 쌍만 벗긴다. 따옴표를 벗긴 뒤에는 더 다듬지 않으므로, 따옴표 안쪽의 빈칸과 쉼표와 또 다른 따옴표는 그대로 남는다. 짝이 맞지 않는 따옴표와 하나뿐인 따옴표는 그대로 둔다. 설정마다 입력에 나온 차례대로 한 줄에 "이름=[값]" 꼴로 출력하라. 값이 빈 문자열이면 "이름=[]"이 된다. 마지막 줄에는 설정의 수를 출력하라.

입력
첫 줄에 n(1 ≤ n ≤ 1,000).
이어서 n줄, 각 줄은 200글자 이하이고 빈 줄일 수 있으며, 탭과 출력 가능한 ASCII 글자(빈칸 포함)로 이루어진다.
건너뛰지 않는 줄에는 '='이 하나 이상 있고, 이름은 영문자·숫자·밑줄 1~20글자다
출력
설정마다 "이름=[값]" 한 줄, 마지막 줄에 설정의 수
예시 1 · stdin
10
  # mode=test
name =  "  Kim  " ,
url=http://a.b/?q=1;
tags = ,a, b,, ;

quote=""hi""
mark = "
mix='x"
   port=8080   
empty =
stdout
name=[  Kim  ]
url=[http://a.b/?q=1]
tags=[,a, b]
quote=["hi"]
mark=["]
mix=['x"]
port=[8080]
empty=[]
8
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

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

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

다듬는 도구가 언어마다 뗄 글자와 방향을 정하는 법이 달라요.
Python은 strip(" \t"), lstrip, rstrip에 뗄 글자들을 문자열로 넘기고, C#은 Trim(' ', '\t'), TrimStart, TrimEnd에 글자를 나열해요.
그래서 값 끝의 네 글자는 Python rstrip(" \t,;"), C# TrimEnd(' ', '\t', ',', ';') 한 번이면 돼요.
JS·TS trim(), Java strip(), 크레이콘 공백제거()는 뗄 글자를 고를 수 없어서, 쉼표와 세미콜론은 끝에서부터 글자를 하나씩 보며 끝 위치를 줄여요.
이 셋은 빈칸과 탭 말고 다른 공백 문자도 떼지만, 이 문제의 줄에는 공백 문자가 빈칸과 탭뿐이라 결과가 같아요.
그래도 정답은 JS·TS에서 정규식 /^[ \t]+/와 /[ \t]+$/로, Java에서 직접 만든 함수로 빈칸과 탭만 떼요.
C에는 다듬는 함수가 없어서 포인터 두 개를 안쪽으로 옮기고, C++는 find_first_not_of와 find_last_not_of로 자리를 찾아 substr로 잘라요.
함정 하나: C++ find_first_not_of는 남는 글자가 없으면 npos를 돌려줘요.
그대로 substr에 넘기면 예시의 빈 줄에서 std::out_of_range로 멈춰요.
함정 둘: 줄을 다듬기 전에 '#'을 확인하면 예시 첫 줄 # mode=test를 설정으로 읽어 # mode=[test]를 출력해요.
함정 셋: '='은 첫 것에서만 나눠요.
Python split("=", 1), Java split("=", 2), C# Split('=', 2)는 나머지를 마지막 조각에 그대로 두지만, JS·TS split("=", 2)는 조각을 2개만 남기고 나머지를 버려서 예시의 url이 http://a.b/?q가 돼요.
indexOf("=")(크레이콘 자리찾기("="))로 자리를 찾아 잘라요.
함정 넷: 값 끝의 네 글자는 한꺼번에 떼야 해요.
rstrip(",;") 다음에 rstrip(" \t")처럼 두 번에 나누면 예시의 tags가 ,a, b,,가 돼요.
함정 다섯: 쉼표와 세미콜론은 끝에서만 떼요.
C# Trim(' ', '\t', ',', ';')처럼 양 끝에서 떼면 tags의 맨 앞 쉼표가 사라져 a, b가 돼요.
함정 여섯: 쉼표를 떼는 일이 따옴표보다 먼저예요.
순서를 바꾸면 예시의 name은 끝 글자가 쉼표라서 따옴표가 벗겨지지 않고 " Kim "이 돼요.
함정 일곱: Python strip('"')와 C# Trim('"')은 한 쌍이 아니라 양 끝의 따옴표를 모두 떼요.
예시의 quote가 "hi"가 아니라 hi가 돼요.
한 쌍은 잘라서 벗겨요.
Python value[1:-1], JS·TS slice(1, -1), Java substring(1, len - 1)은 끝 위치를 주고, C# Substring(1, len - 2), C++ substr(1, len - 2), 크레이콘 부분문자열(1, len - 2)는 길이를 줘요.
함정 여덟: 길이가 2 이상인지 봐야 해요.
따옴표 하나뿐인 값은 첫 글자와 끝 글자가 같은 글자예요.
확인하지 않으면 예시의 mark가 Python에서는 mark=[]가 되고, Java에서는 substring(1, 0)이 StringIndexOutOfBoundsException으로 멈춰요.
함정 아홉: JS 정규식 /^["']|["']$/g로 앞뒤 따옴표를 따로 지우면 짝이 맞지 않는 예시의 mix가 x가 돼요.
함정 열: 따옴표를 벗긴 뒤에 다시 다듬으면 name이 Kim 이 아니라 Kim이 돼요.

Pythonmain.py · 20 줄
import sys lines = sys.stdin.read().split("\n")n = int(lines[0])out = []count = 0for line in lines[1:n + 1]:    line = line.strip(" \t")               # 먼저 줄의 양 끝을 다듬는다    if line == "" or line[0] == "#":       # 빈 줄과 주석은 건너뛴다        continue    eq = line.index("=")                   # 첫 '='에서만 나눈다    key = line[:eq].rstrip(" \t")    value = line[eq + 1:].lstrip(" \t")    value = value.rstrip(" \t,;")          # 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다    if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":        value = value[1:-1]                # 같은 따옴표 한 쌍만 벗긴다    out.append(f"{key}=[{value}]")    count += 1out.append(str(count))print("\n".join(out))
JavaScriptmain.js · 22 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const out = [];let count = 0;for (let i = 1; i <= n; i++) {  // trim()은 다른 공백 문자까지 떼므로 빈칸과 탭만 정규식으로 뗀다  const line = lines[i].replace(/^[ \t]+/, "").replace(/[ \t]+$/, "");  if (line === "" || line[0] === "#") continue;  const eq = line.indexOf("="); // 첫 '='에서만 나눈다  const key = line.slice(0, eq).replace(/[ \t]+$/, "");  let value = line.slice(eq + 1).replace(/^[ \t]+/, "");  let end = value.length; // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다  while (end > 0 && " \t,;".includes(value[end - 1])) end--;  value = value.slice(0, end);  if (value.length >= 2 && value[0] === value[value.length - 1] && (value[0] === '"' || value[0] === "'")) {    value = value.slice(1, -1); // 같은 따옴표 한 쌍만 벗긴다  }  out.push(`${key}=[${value}]`);  count++;}out.push(String(count));console.log(out.join("\n"));
TypeScriptmain.ts · 24 줄
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const out: string[] = [];let count = 0;for (let i = 1; i <= n; i++) {  // trim()은 다른 공백 문자까지 떼므로 빈칸과 탭만 정규식으로 뗀다  const line = lines[i].replace(/^[ \t]+/, "").replace(/[ \t]+$/, "");  if (line === "" || line[0] === "#") continue;  const eq = line.indexOf("="); // 첫 '='에서만 나눈다  const key = line.slice(0, eq).replace(/[ \t]+$/, "");  let value = line.slice(eq + 1).replace(/^[ \t]+/, "");  let end = value.length; // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다  while (end > 0 && " \t,;".includes(value.charAt(end - 1))) end--;  value = value.slice(0, end);  if (value.length >= 2 && value[0] === value[value.length - 1] && (value[0] === '"' || value[0] === "'")) {    value = value.slice(1, -1); // 같은 따옴표 한 쌍만 벗긴다  }  out.push(`${key}=[${value}]`);  count++;}out.push(String(count));console.log(out.join("\n"));
JavaMain.java · 42 줄
import java.io.BufferedReader;import java.io.InputStreamReader; public class Main {    static boolean blank(char c) {        return c == ' ' || c == '\t';    }     // s[from, to)에서 양 끝의 빈칸과 탭을 뗀 조각    static String trim(String s, int from, int to) {        while (from < to && blank(s.charAt(from))) from++;        while (to > from && blank(s.charAt(to - 1))) to--;        return s.substring(from, to);    }     public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        int count = 0;        for (int i = 0; i < n; i++) {            String raw = in.readLine();            String line = trim(raw, 0, raw.length());            if (line.isEmpty() || line.charAt(0) == '#') continue;            int eq = line.indexOf('=');                    // 첫 '='에서만 나눈다            String key = trim(line, 0, eq);            String value = trim(line, eq + 1, line.length());            int end = value.length();                      // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다            while (end > 0 && " \t,;".indexOf(value.charAt(end - 1)) >= 0) end--;            value = value.substring(0, end);            int len = value.length();            if (len >= 2 && value.charAt(0) == value.charAt(len - 1)                    && (value.charAt(0) == '"' || value.charAt(0) == '\'')) {                value = value.substring(1, len - 1);       // substring(시작, 끝): 같은 따옴표 한 쌍만 벗긴다            }            out.append(key).append("=[").append(value).append("]\n");            count++;        }        out.append(count).append('\n');        System.out.print(out);    }}
C#Main.cs · 29 줄
using System;using System.Text; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine()!.Trim());        var sb = new StringBuilder();        int count = 0;        for (int i = 0; i < n; i++)        {            string line = Console.ReadLine()!.Trim(' ', '\t');            if (line.Length == 0 || line[0] == '#') continue;            int eq = line.IndexOf('=');                         // 첫 '='에서만 나눈다            string key = line.Substring(0, eq).TrimEnd(' ', '\t');            string value = line.Substring(eq + 1).TrimStart(' ', '\t');            value = value.TrimEnd(' ', '\t', ',', ';');         // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다            if (value.Length >= 2 && value[0] == value[value.Length - 1] && (value[0] == '"' || value[0] == '\''))            {                value = value.Substring(1, value.Length - 2);   // Substring(시작, 길이): 같은 따옴표 한 쌍만 벗긴다            }            sb.Append(key).Append("=[").Append(value).Append("]\n");            count++;        }        sb.Append(count).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 38 줄
#include <iostream>#include <string> // 양 끝에서 chars에 든 글자를 뗀다. 남는 글자가 없으면 npos가 나오므로 따로 처리한다static std::string trimLeft(const std::string& s, const char* chars) {    std::size_t p = s.find_first_not_of(chars);    return p == std::string::npos ? "" : s.substr(p);} static std::string trimRight(const std::string& s, const char* chars) {    std::size_t p = s.find_last_not_of(chars);    return p == std::string::npos ? "" : s.substr(0, p + 1);} int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    std::string line;    std::getline(std::cin, line);    int n = std::stoi(line);    int count = 0;    for (int i = 0; i < n; i++) {        std::getline(std::cin, line);        line = trimRight(trimLeft(line, " \t"), " \t");        if (line.empty() || line[0] == '#') continue;        std::size_t eq = line.find('=');                          // 첫 '='에서만 나눈다        std::string key = trimRight(line.substr(0, eq), " \t");        std::string value = trimLeft(line.substr(eq + 1), " \t");        value = trimRight(value, " \t,;");                        // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다        if (value.size() >= 2 && value.front() == value.back() && (value[0] == '"' || value[0] == '\'')) {            value = value.substr(1, value.size() - 2);            // substr(시작, 길이): 같은 따옴표 한 쌍만 벗긴다        }        std::cout << key << "=[" << value << "]\n";        count++;    }    std::cout << count << "\n";    return 0;}
Cmain.c · 33 줄
#include <stdio.h>#include <string.h> static int blank(char c) { return c == ' ' || c == '\t'; } int main(void) {    static char buf[256];    int n, count = 0;    if (!fgets(buf, sizeof buf, stdin) || sscanf(buf, "%d", &n) != 1) return 0;    for (int i = 0; i < n; i++) {        if (!fgets(buf, sizeof buf, stdin)) break;        char *a = buf;        char *b = buf + strcspn(buf, "\r\n");          /* [a, b)가 줄의 내용 */        while (a < b && blank(*a)) a++;                /* 줄의 양 끝을 다듬는다 */        while (b > a && blank(b[-1])) b--;        if (a == b || *a == '#') continue;        char *eq = memchr(a, '=', (size_t)(b - a));    /* 첫 '='에서만 나눈다 */        char *ke = eq;        while (ke > a && blank(ke[-1])) ke--;          /* 이름은 [a, ke) */        char *v = eq + 1;        while (v < b && blank(*v)) v++;        /* 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다 */        while (b > v && (blank(b[-1]) || b[-1] == ',' || b[-1] == ';')) b--;        if (b - v >= 2 && *v == b[-1] && (*v == '"' || *v == '\'')) {            v++;                                       /* 같은 따옴표 한 쌍만 벗긴다 */            b--;        }        printf("%.*s=[%.*s]\n", (int)(ke - a), a, (int)(b - v), v);        count++;    }    printf("%d\n", count);    return 0;}
Craykonmain.cray · 27 줄
n = 콘솔.줄읽기().수로변환();count = 0;반복 (i = 0; i < n; i++){    line = 콘솔.줄읽기().공백제거(); // 먼저 줄의 양 끝을 다듬는다    만약 (line == "" || line[0] == "#")    {        건너뛰기;    }    eq = line.자리찾기("="); // 첫 '='에서만 나눈다    key = line.부분문자열(0, eq).공백제거();    value = line.부분문자열(eq + 1).공백제거();    end = value.길이; // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다    동안 (end > 0 && " \t,;".있나(value[end - 1]))    {        end -= 1;    }    value = value.부분문자열(0, end);    len = value.길이;    만약 (len >= 2 && value[0] == value[len - 1] && (value[0] == "\"" || value[0] == "'"))    {        value = value.부분문자열(1, len - 2); // 부분문자열(시작, 길이): 같은 따옴표 한 쌍만 벗긴다    }    콘솔.쓰기(key + "=[" + value + "]");    count += 1;}콘솔.쓰기(count);