{ }codelper
空白の除去文字列の検索部分文字列P0393

設定ファイルの値を整える

設定ファイルの行を読み、名前と値を整える。
最初の行に行数 n が与えられ、続いて n 行が与えられる。
各行を次の順序で処理する。
まず行の両端から空白とタブをすべて取り除く。
その結果が空文字列であるか '#' で始まるなら、設定ではないので読み飛ばす。
それ以外の行は、最初に現れる '=' で 2 つに分ける。
'=' の左側の両端から空白とタブを取り除いたものが名前である。
'=' の右側は値であり、その中にさらに '=' や '#' があっても値の一部である。
値は両端の空白とタブを取り除いたあと、末尾から空白、タブ、カンマ(',')、セミコロン(';')が続く間、この 4 種類の文字をすべて取り除く。
カンマとセミコロンはこのように末尾からだけ取り除き、値の前側からは取り除かないが、残った文字がすべてこの 4 種類の文字なら全部取り除かれて値は空文字列になる。
そのあと、値の長さが 2 以上で、最初の文字と最後の文字が同じ引用符(どちらも二重引用符か、どちらも一重引用符)なら、その 1 組だけを外す。
引用符を外したあとはそれ以上整えないので、引用符の内側の空白、カンマ、別の引用符はそのまま残る。
組になっていない引用符と 1 つだけの引用符はそのままにする。
設定ごとに入力に現れた順に、1 行に "名前=[値]" の形で出力せよ。
値が空文字列なら "名前=[]" となる。
最後の行には設定の数を出力せよ。

入力
最初の行に n(1 ≤ n ≤ 1,000)。
続いて n 行。
各行は 200 文字以下で、空行のこともあり、タブと表示可能な ASCII 文字(空白を含む)からなる。
読み飛ばさない行には '=' が 1 つ以上あり、名前は英字・数字・下線 1〜20 文字である
出力
設定ごとに「名前=[値]」を 1 行、最後の行に設定の数
例 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()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

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

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

整える道具は、取り除く文字と向きの決め方が言語ごとに違います。
Python は strip(" \t")、lstrip、rstrip に取り除く文字を文字列で渡し、C# は Trim(' ', '\t')、TrimStart、TrimEnd に文字を並べます。
そのため値の末尾の 4 種類の文字は、Python rstrip(" \t,;")、C# TrimEnd(' ', '\t', ',', ';') の 1 回で済みます。
JS・TS trim()、Java strip()、Craykon 空白除去() は取り除く文字を選べないので、カンマとセミコロンは末尾から 1 文字ずつ見て終わりの位置を戻します。
この 3 つは空白とタブ以外の空白文字も取り除きますが、この問題の行には空白文字が空白とタブしかないので結果は同じです。
それでも解答は、JS・TS では正規表現 /^[ \t]+/ と /[ \t]+$/ で、Java では自作の関数で空白とタブだけを取り除きます。
C には整える関数がないのでポインタ 2 つを内側に動かし、C++ は find_first_not_of と find_last_not_of で位置を探して substr で切ります。
落とし穴その一:C++ find_first_not_of は残る文字がないと npos を返します。
そのまま substr に渡すと、例の空行で std::out_of_range で止まります。
落とし穴その二:行を整える前に '#' を調べると、例の 1 行目 # mode=test を設定として読み、# mode=[test] を出力します。
落とし穴その三:'=' は最初のものだけで分けます。
Python split("=", 1)、Java split("=", 2)、C# Split('=', 2) は残りを最後の切れ端にそのまま残しますが、JS・TS split("=", 2) は切れ端を 2 個だけ残して残りを捨てるので、例の url が http://a.b/?q になります。
indexOf("=")(Craykon 位置検索("="))で位置を探して切ります。
落とし穴その四:値の末尾の 4 種類の文字はまとめて取り除く必要があります。
rstrip(",;") のあとに rstrip(" \t") と 2 回に分けると、例の tags が ,a, b,, になります。
落とし穴その五:カンマとセミコロンは末尾からだけ取り除きます。
C# Trim(' ', '\t', ',', ';') のように両端から取り除くと、tags の先頭のカンマが消えて a, b になります。
落とし穴その六:カンマを取り除くのは引用符より先です。
順序を逆にすると、例の name は最後の文字がカンマなので引用符が外れず、" Kim " になります。
落とし穴その七:Python strip('"') と C# Trim('"') は 1 組ではなく両端の引用符をすべて取り除きます。
例の quote が "hi" ではなく hi になります。
1 組は切り出して外します。
Python value[1:-1]、JS・TS slice(1, -1)、Java substring(1, len - 1) は終わりの位置を渡し、C# Substring(1, len - 2)、C++ substr(1, len - 2)、Craykon 切り出す(1, len - 2) は長さを渡します。
落とし穴その八:長さが 2 以上かどうかを調べる必要があります。
引用符 1 つだけの値は、最初の文字と最後の文字が同じ文字です。
調べないと、例の 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);