{ }codelper
trimmingfinding textsubstringsP0393

Trimming values in a config file

Read the lines of a config file and trim each name and value. The first line gives the number of lines n, and n lines follow. Process each line in this order. First remove all spaces and tabs from both ends of the line. If the result is empty or starts with '#', it is not a setting and is skipped. Every other line is cut in two at the first '='. The left part with spaces and tabs removed from both ends is the name. The right part is the value, and any further '=' or '#' inside it belongs to the value. For the value, remove spaces and tabs from both ends, and then, starting from the end, remove all of these four characters as long as they continue: space, tab, comma (',') and semicolon (';'). Commas and semicolons are removed only from the end in this way and never from the front, but if every remaining character is one of these four, all of them are removed and the value becomes the empty string. After that, if the value has length at least 2 and its first and last characters are the same quote (both double quotes or both single quotes), remove just that one pair. Nothing is trimmed after removing the quotes, so spaces, commas and further quotes inside the quotes stay as they are. Quotes that do not match and a lone quote are left alone. For each setting, in input order, print one line in the form "name=[value]". If the value is empty, the line is "name=[]". On the last line print the number of settings.

Input
The first line has n (1 ≤ n ≤ 1,000).
Then n lines follow, each at most 200 characters, possibly empty, made of tabs and printable ASCII characters (including spaces).
Every line that is not skipped has at least one '=', and its name is 1 to 20 letters, digits or underscores
Output
One line "name=[value]" per setting, then the number of settings on the last line
Example 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
Input (stdin)

When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).

$ Ready

Open this on a computer to write and run code. On mobile you can read the problem and the solutions.

In other languagesShow solutions by language · 8 languages

The trimming tools differ in how you choose the characters and the side.
Python takes the characters as a string in strip(" \t"), lstrip and rstrip, and C# lists them in Trim(' ', '\t'), TrimStart and TrimEnd.
So the four characters at the end of the value need just one call: Python rstrip(" \t,;"), C# TrimEnd(' ', '\t', ',', ';').
JS/TS trim(), Java strip() and Craykon Trim() cannot choose the characters, so for commas and semicolons you look at characters one by one from the end and move the end position back.
These three also remove whitespace other than spaces and tabs, but the lines here have no other whitespace, so the result is the same.
Even so, the solutions remove only spaces and tabs, with the regexes /^[ \t]+/ and /[ \t]+$/ in JS/TS and a hand-written function in Java.
C has no trimming function, so you move two pointers inward, and C++ finds positions with find_first_not_of and find_last_not_of and cuts with substr.
Trap one: C++ find_first_not_of returns npos when no character is left.
Passing it straight to substr stops with std::out_of_range on the empty line of the example.
Trap two: checking for '#' before trimming the line reads the first example line # mode=test as a setting and prints # mode=[test].
Trap three: cut only at the first '='.
Python split("=", 1), Java split("=", 2) and C# Split('=', 2) keep the rest in the last piece, but JS/TS split("=", 2) keeps only 2 pieces and throws the rest away, so url in the example becomes http://a.b/?q.
Find the position with indexOf("=") (Craykon IndexOf("=")) and cut there.
Trap four: the four characters at the end must be removed together.
Splitting it into rstrip(",;") and then rstrip(" \t") turns tags in the example into ,a, b,,.
Trap five: commas and semicolons are removed only at the end.
Removing them at both ends as in C# Trim(' ', '\t', ',', ';') loses the leading comma of tags and gives a, b.
Trap six: commas are removed before quotes are handled.
In the other order, name in the example ends with a comma, so the quotes are not removed and you get " Kim ".
Trap seven: Python strip('"') and C# Trim('"') remove not one pair but every quote at both ends.
quote in the example becomes hi instead of "hi".
Remove one pair by slicing.
Python value[1:-1], JS/TS slice(1, -1), Java substring(1, len - 1) takes the end position, while C# Substring(1, len - 2), C++ substr(1, len - 2) and Craykon Substring(1, len - 2) take a length.
Trap eight: you must check that the length is at least 2.
In a value that is a single quote, the first and last characters are the same character.
Without the check, mark in the example becomes mark=[] in Python, and in Java substring(1, 0) stops with StringIndexOutOfBoundsException.
Trap nine: deleting the front and back quotes separately with the JS regex /^["']|["']$/g turns the mismatched mix of the example into x.
Trap ten: trimming again after removing the quotes turns name into Kim instead of Kim .

Pythonmain.py · 20 lines
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 lines
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 lines
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 lines
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 lines
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 lines
#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 lines
#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 lines
n = Console.ReadLine().ToNumber();count = 0;for (i = 0; i < n; i++){    line = Console.ReadLine().Trim(); // 먼저 줄의 양 끝을 다듬는다    if (line == "" || line[0] == "#")    {        continue;    }    eq = line.IndexOf("="); // 첫 '='에서만 나눈다    key = line.Substring(0, eq).Trim();    value = line.Substring(eq + 1).Trim();    end = value.Length; // 끝에서만, 네 글자 중 무엇이든 한꺼번에 뗀다    while (end > 0 && " \t,;".Contains(value[end - 1]))    {        end -= 1;    }    value = value.Substring(0, end);    len = value.Length;    if (len >= 2 && value[0] == value[len - 1] && (value[0] == "\"" || value[0] == "'"))    {        value = value.Substring(1, len - 2); // 부분문자열(시작, 길이): 같은 따옴표 한 쌍만 벗긴다    }    Console.WriteLine(key + "=[" + value + "]");    count += 1;}Console.WriteLine(count);