{ }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()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

各语言的整理工具在如何指定要去掉的字符和方向上并不相同。
Python 把要去掉的字符作为字符串传给 strip(" \t")、lstrip、rstrip,C# 则在 Trim(' ', '\t')、TrimStart、TrimEnd 中列出字符。
所以值末尾的四种字符只需调用一次:Python rstrip(" \t,;"),C# TrimEnd(' ', '\t', ',', ';')。
JS/TS trim()、Java strip()、Craykon 去空白() 不能选择要去掉的字符,所以逗号和分号要从末尾一个一个地看字符,把结束位置往前移。
这三个函数还会去掉空格和制表符以外的空白字符,不过本题的行里空白字符只有空格和制表符,所以结果相同。
尽管如此,解答在 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 而停止。
陷阱二:在整理行之前检查 '#',会把示例第 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 查找位置("="))找到位置再截取。
陷阱四:值末尾的四种字符必须一起去掉。
分成 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)、Craykon 截取(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);