{ }codelper
문자열 나누기문자열 돌기입력 끝까지 읽기P0105

따옴표가 있는 CSV 나누기

CSV 형식의 레코드가 입력이 끝날 때까지 한 줄에 하나씩 주어진다. 한 줄은 쉼표로 나뉜 필드들이다. 큰따옴표(")로 시작하는 필드는 따옴표 필드다.
여는 따옴표와 닫는 따옴표 사이가 필드의 내용이다. 따옴표 필드 안의 쉼표는 나누는 기호가 아니라 글자이고, 안의 ""(따옴표 두 개)는 따옴표 한 글자를 뜻한다. 닫는 따옴표 바로 뒤에는 쉼표가 오거나 줄이 끝난다. 따옴표로 시작하지 않는 필드는 다음 쉼표나 줄 끝까지의 글자 그대로이고, 안에 따옴표가 없다. 빈칸도 필드의 글자이니 지우지 않는다. 쉼표가 이어지거나(,,) 줄이 쉼표로 시작하거나 끝나면 그 자리에 빈 필드가 있다. 레코드마다 필드 수를 쓰고, 빈칸 하나 뒤에 필드들을 하나씩 대괄호로 감싸 이어 붙인 것을 한 줄에 출력하라.

입력
줄은 1 ~ 1,000개이고, 줄마다 1 ~ 200글자다.
글자는 출력 가능한 ASCII이며 [ 와 ] 는 나오지 않는다.
모든 줄은 위 규칙에 맞는 CSV다
출력
레코드마다 "필드수 [필드1][필드2]..." 한 줄
예시 1 · stdin
name,"Lee, Mina",42
"say ""hi""",,x,
 a ,b
stdout
3 [name][Lee, Mina][42]
4 [say "hi"][][x][]
2 [ a ][b]
main.py
입력(stdin)

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

$ 준비됐어요

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

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

쉼표로 그냥 나누면(split(",")) 따옴표 안의 쉼표에서도 잘려요.
글자를 하나씩 보며 "따옴표 안인가"를 기억하는 작은 상태 기계가 필요해요.
따옴표 안에서 "를 만나면 다음 글자도 "인지 봐요.
그렇다면 따옴표 한 글자이고, 아니면 필드가 끝나요.
""를 줄이지 않으면 say ""hi""가 나와요.
빈 필드도 언어마다 함정이 있어요.
Python split, JS split, C# Split은 빈 필드를 남겨요.
Java String.split(",")은 끝의 빈 문자열을 버려서 "a,b,"가 2개가 돼요(split(",", -1)로 막아요).
C strtok은 붙은 쉼표를 하나로 보고 빈 필드를 아예 건너뛰어요.
이 문제의 C 풀이는 strtok 없이 글자를 하나씩 봐요.
Python에는 표준 csv 모듈이 있어서 csv.reader(sys.stdin) 한 줄이면 돼요.
C#에도 Microsoft.VisualBasic.FileIO.TextFieldParser가 있지만, 기본으로 필드 앞뒤 빈칸을 지워서(TrimWhiteSpace가 true) " a "가 "a"가 돼요.
쓰려면 TrimWhiteSpace = false로 둬요.
Java·JS·TS·C·C++에는 표준 CSV 도구가 없어서 직접 써요.
입력 끝까지 읽기는 Java·C# readLine()이 null, C fgets가 NULL, C++ getline이 거짓일 때까지예요.
크레이콘은 콘솔.줄읽기()가 빈값일 때까지 읽고, s[i]로 글자를 하나씩 봐요.
따옴표 글자는 "\""로 써요.

Pythonmain.py · 6 줄
import csvimport sys # csv 모듈이 따옴표 필드, 안의 쉼표, "" → " 를 모두 처리해 준다for fields in csv.reader(sys.stdin):    print(len(fields), "".join(f"[{f}]" for f in fields))
JavaScriptmain.js · 30 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");if (lines.at(-1) === "") lines.pop(); // 마지막 줄 바꿈 뒤의 빈 조각 function parse(s) {  const fields = [];  let i = 0;  while (true) {    let field = "";    if (s[i] === '"') {      i++; // 여는 따옴표      while (true) {        if (s[i] === '"') {          if (s[i + 1] === '"') { field += '"'; i += 2; } // "" → "          else { i++; break; } // 닫는 따옴표        } else field += s[i++];      }    } else {      let j = s.indexOf(",", i);      if (j === -1) j = s.length;      field = s.slice(i, j);      i = j;    }    fields.push(field);    if (i >= s.length) break; // 줄 끝    i++; // 쉼표 건너뛰기(쉼표로 끝나면 다음 바퀴에 빈 필드)  }  return fields;} console.log(lines.map((s) => { const f = parse(s); return `${f.length} ` + f.map((x) => `[${x}]`).join(""); }).join("\n"));
TypeScriptmain.ts · 31 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");if (lines.at(-1) === "") lines.pop(); // 마지막 줄 바꿈 뒤의 빈 조각 function parse(s: string): string[] {  const fields: string[] = [];  let i = 0;  while (true) {    let field = "";    if (s[i] === '"') {      i++; // 여는 따옴표      while (true) {        if (s[i] === '"') {          if (s[i + 1] === '"') { field += '"'; i += 2; } // "" → "          else { i++; break; } // 닫는 따옴표        } else field += s[i++];      }    } else {      let j: number = s.indexOf(",", i);      if (j === -1) j = s.length;      field = s.slice(i, j);      i = j;    }    fields.push(field);    if (i >= s.length) break; // 줄 끝    i++; // 쉼표 건너뛰기(쉼표로 끝나면 다음 바퀴에 빈 필드)  }  return fields;} console.log(lines.map((s: string): string => { const f = parse(s); return `${f.length} ` + f.map((x) => `[${x}]`).join(""); }).join("\n"));
JavaMain.java · 47 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; public class Main {    static List<String> parse(String s) {        List<String> fields = new ArrayList<>();        int i = 0;        while (true) {            StringBuilder field = new StringBuilder();            if (i < s.length() && s.charAt(i) == '"') {                i++; // 여는 따옴표                while (true) {                    char c = s.charAt(i);                    if (c == '"') {                        if (i + 1 < s.length() && s.charAt(i + 1) == '"') { field.append('"'); i += 2; } // "" → "                        else { i++; break; } // 닫는 따옴표                    } else { field.append(c); i++; }                }            } else {                int j = s.indexOf(',', i);                if (j == -1) j = s.length();                field.append(s, i, j);                i = j;            }            fields.add(field.toString());            if (i >= s.length()) break;            i++; // 쉼표        }        return fields;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringBuilder out = new StringBuilder();        String line;        while ((line = in.readLine()) != null) {            List<String> f = parse(line);            out.append(f.size()).append(' ');            for (String x : f) out.append('[').append(x).append(']');            out.append('\n');        }        System.out.print(out);    }}
C#Main.cs · 54 줄
using System;using System.Collections.Generic;using System.Text; class Program{    static List<string> Parse(string s)    {        var fields = new List<string>();        int i = 0;        while (true)        {            var field = new StringBuilder();            if (i < s.Length && s[i] == '"')            {                i++; // 여는 따옴표                while (true)                {                    if (s[i] == '"')                    {                        if (i + 1 < s.Length && s[i + 1] == '"') { field.Append('"'); i += 2; } // "" → "                        else { i++; break; } // 닫는 따옴표                    }                    else field.Append(s[i++]);                }            }            else            {                int j = s.IndexOf(',', i);                if (j == -1) j = s.Length;                field.Append(s, i, j - i);                i = j;            }            fields.Add(field.ToString());            if (i >= s.Length) break;            i++; // 쉼표        }        return fields;    }     static void Main()    {        var sb = new StringBuilder();        string line;        while ((line = Console.ReadLine()) != null)        {            var f = Parse(line);            sb.Append(f.Count).Append(' ');            foreach (string x in f) sb.Append('[').Append(x).Append(']');            sb.Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 39 줄
#include <iostream>#include <string>#include <vector> static std::vector<std::string> parse(const std::string& s) {    std::vector<std::string> fields;    std::size_t i = 0;    while (true) {        std::string field;        if (i < s.size() && s[i] == '"') {            i++; // 여는 따옴표            while (true) {                if (s[i] == '"') {                    if (i + 1 < s.size() && s[i + 1] == '"') { field += '"'; i += 2; } // "" → "                    else { i++; break; }                                               // 닫는 따옴표                } else field += s[i++];            }        } else {            auto j = s.find(',', i);            if (j == std::string::npos) j = s.size();            field = s.substr(i, j - i);            i = j;        }        fields.push_back(field);        if (i >= s.size()) break;        i++; // 쉼표    }    return fields;} int main() {    std::string line;    while (std::getline(std::cin, line)) {        auto f = parse(line);        std::cout << f.size() << ' ';        for (const auto& x : f) std::cout << '[' << x << ']';        std::cout << '\n';    }}
Cmain.c · 32 줄
#include <stdio.h>#include <string.h> int main(void) {    static char s[512];    static char out[1024];    while (fgets(s, sizeof s, stdin)) {        s[strcspn(s, "\n")] = '\0';        int len = (int)strlen(s), i = 0, count = 0, o = 0;        while (1) {            out[o++] = '[';            if (s[i] == '"') {                i++; /* 여는 따옴표 */                while (1) {                    if (s[i] == '"') {                        if (s[i + 1] == '"') { out[o++] = '"'; i += 2; } /* "" → " */                        else { i++; break; }                              /* 닫는 따옴표 */                    } else out[o++] = s[i++];                }            } else {                while (i < len && s[i] != ',') out[o++] = s[i++];            }            out[o++] = ']';            count++;            if (i >= len) break;            i++; /* 쉼표 */        }        out[o] = '\0';        printf("%d %s\n", count, out);    }    return 0;}
Craykonmain.cray · 63 줄
s = 콘솔.줄읽기();동안 (s != 빈값){    fields = parseLine(s);    out = fields.개수 + " ";    각각 (f 에서 fields)    {        out += "[" + f + "]";    }    콘솔.쓰기(out);    s = 콘솔.줄읽기();} // 한 줄을 필드 목록으로: 따옴표 필드 안의 쉼표는 글자, "" 는 따옴표 하나parseLine(s){    fields = [];    i = 0;    동안 (참)    {        field = "";        만약 (i < s.길이 && s[i] == "\"")        {            i += 1;            동안 (참)            {                만약 (s[i] == "\"")                {                    만약 (i + 1 < s.길이 && s[i + 1] == "\"")                    {                        field += "\"";                        i += 2;                    }                    아니면                    {                        i += 1;                        그만;                    }                }                아니면                {                    field += s[i];                    i += 1;                }            }        }        아니면        {            동안 (i < s.길이 && s[i] != ",")            {                field += s[i];                i += 1;            }        }        fields.넣기(field);        만약 (i >= s.길이)        {            그만;        }        i += 1;    }    돌려주기 fields;}