C ++에서 CSV 파일을 읽고 구문 분석하는 방법


264

C ++에서 CSV 파일 데이터를로드하고 사용해야합니다. 이 시점에서 실제로는 쉼표로 구분 된 파서 일 수 있습니다 (즉, 새 줄과 쉼표를 피하는 것에 대해 걱정하지 마십시오). 주요 요구 사항은 한 줄씩 파서인데,이 메서드는 메서드가 호출 될 때마다 다음 줄의 벡터를 반환합니다.

나는 유망하게 보이는이 기사를 발견했다 : http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

나는 Boost 's Spirit을 사용한 적이 없지만 그것을 기꺼이 시도합니다. 그러나 더 간단한 해결책이없는 경우에만 간과하고 있습니다.


11
나는 boost::spirit파싱 을 보았다 . 문법을 파싱하는 것은 간단한 파일 형식을 파싱하는 것이 좋습니다. 우리 팀의 누군가가 XML을 구문 분석하는 데 사용하려고 시도했으며 디버깅하기가 어려웠습니다. boost::spirit가능하면 멀리 하십시오.
chrish

50
미안 chrish, 그러나 그것은 끔찍한 충고입니다. Spirit은 항상 적절한 솔루션은 아니지만 여러 프로젝트에서 성공적으로 사용해 왔으며 계속 사용하고 있습니다. 유사한 도구 (Antlr, Lex / yacc 등)와 비교할 때 상당한 이점이 있습니다. 이제, CSV를 파싱하는 것은 아마 과잉 일 것입니다.
MattyT

4
@MattyT IMHO spirit는 파서 결합기 라이브러리에 사용하기가 매우 어렵습니다. Haskells (atto)parsec라이브러리에 대해 (매우 유쾌한) 경험이 있었지만 비슷한 방식으로 작동하지만 600 라인 컴파일러 오류와 싸운 후에 포기했습니다.
fho

답변:


296

쉼표와 줄 바꿈을 신경 쓰지 않고
쉼표와 줄 바꿈을 따옴표로 묶을 수 없다면 (이스케이프 할 수 없다면 ...)
약 세 줄의 코드 (OK 14-> 그러나 전체 파일을 읽으려면 15입니다.)

std::vector<std::string> getNextLineAndSplitIntoTokens(std::istream& str)
{
    std::vector<std::string>   result;
    std::string                line;
    std::getline(str,line);

    std::stringstream          lineStream(line);
    std::string                cell;

    while(std::getline(lineStream,cell, ','))
    {
        result.push_back(cell);
    }
    // This checks for a trailing comma with no data after it.
    if (!lineStream && cell.empty())
    {
        // If there was a trailing comma then add an empty element.
        result.push_back("");
    }
    return result;
}

그냥 행을 나타내는 클래스를 만들 것입니다.
그런 다음 해당 객체로 스트리밍하십시오.

#include <iterator>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

class CSVRow
{
    public:
        std::string const& operator[](std::size_t index) const
        {
            return m_data[index];
        }
        std::size_t size() const
        {
            return m_data.size();
        }
        void readNextRow(std::istream& str)
        {
            std::string         line;
            std::getline(str, line);

            std::stringstream   lineStream(line);
            std::string         cell;

            m_data.clear();
            while(std::getline(lineStream, cell, ','))
            {
                m_data.push_back(cell);
            }
            // This checks for a trailing comma with no data after it.
            if (!lineStream && cell.empty())
            {
                // If there was a trailing comma then add an empty element.
                m_data.push_back("");
            }
        }
    private:
        std::vector<std::string>    m_data;
};

std::istream& operator>>(std::istream& str, CSVRow& data)
{
    data.readNextRow(str);
    return str;
}   
int main()
{
    std::ifstream       file("plop.csv");

    CSVRow              row;
    while(file >> row)
    {
        std::cout << "4th Element(" << row[3] << ")\n";
    }
}

그러나 약간의 작업으로 기술적으로 반복자를 만들 수 있습니다.

class CSVIterator
{   
    public:
        typedef std::input_iterator_tag     iterator_category;
        typedef CSVRow                      value_type;
        typedef std::size_t                 difference_type;
        typedef CSVRow*                     pointer;
        typedef CSVRow&                     reference;

        CSVIterator(std::istream& str)  :m_str(str.good()?&str:NULL) { ++(*this); }
        CSVIterator()                   :m_str(NULL) {}

        // Pre Increment
        CSVIterator& operator++()               {if (m_str) { if (!((*m_str) >> m_row)){m_str = NULL;}}return *this;}
        // Post increment
        CSVIterator operator++(int)             {CSVIterator    tmp(*this);++(*this);return tmp;}
        CSVRow const& operator*()   const       {return m_row;}
        CSVRow const* operator->()  const       {return &m_row;}

        bool operator==(CSVIterator const& rhs) {return ((this == &rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));}
        bool operator!=(CSVIterator const& rhs) {return !((*this) == rhs);}
    private:
        std::istream*       m_str;
        CSVRow              m_row;
};


int main()
{
    std::ifstream       file("plop.csv");

    for(CSVIterator loop(file); loop != CSVIterator(); ++loop)
    {
        std::cout << "4th Element(" << (*loop)[3] << ")\n";
    }
}

20
first () next (). 이 Java는 무엇입니까! 그냥 농담.
Martin York

4
@DarthVader : 넓게 보면 어리석은 오버레이 넓은 진술. 왜 그것이 나쁜지, 그리고 왜이 나쁜가이 맥락에서 적용되는지를 분명히하고 싶다면.
Martin York

12
@DarthVader : 폭 넓은 일반화를하는 것은 어리석은 일이라고 생각합니다. 위의 코드는 올바르게 작동하므로 실제로 문제가 있음을 알 수 있습니다. 그러나 위의 특정 의견이 있으면이 맥락에서 분명히 고려할 것입니다. 그러나 C #에 대한 일반화 된 규칙 집합을 무의식적으로 따르고 다른 언어에 적용하여 이러한 결론에 도달 할 수있는 방법을 알 수 있습니다.
Martin York

5
또한 다른 라이브러리 istream::operator>>(예 : Eigen)를 정의하기 때문에 위의 코드에서 이상한 연결 문제가 발생 inline하면 연산자 선언 앞에 앞에 추가 하여 수정하십시오.
sebastian_k 2016 년

3
이것은 내가 본 반복자 클래스를 만드는 방법에 대한 가장 간단하고 깨끗한 예입니다.
지안 Sportelli

46

Boost Tokenizer를 사용하는 솔루션 :

std::vector<std::string> vec;
using namespace boost;
tokenizer<escaped_list_separator<char> > tk(
   line, escaped_list_separator<char>('\\', ',', '\"'));
for (tokenizer<escaped_list_separator<char> >::iterator i(tk.begin());
   i!=tk.end();++i) 
{
   vec.push_back(*i);
}

9
부스트 토크 나이 저는 완전한 CSV 표준을 완벽하게 지원하지는 않지만 몇 가지 빠른 해결 방법이 있습니다. stackoverflow.com/questions/1120140/csv-parser-in-c/…를
Rolf Kristensen

3
컴퓨터에 전체 부스트 라이브러리가 있어야합니까, 아니면 코드의 하위 세트를 사용하여이를 수행 할 수 있습니까? 256mb는 CSV 구문 분석에 많은 것 같습니다.
NPike

6
@NPike : boost와 함께 제공 되는 bcp 유틸리티를 사용하여 실제로 필요한 헤더 만 추출 할 수 있습니다.
ildjarn

46

내 버전은 표준 C ++ 11 라이브러리 이외의 것을 사용하지 않습니다. Excel CSV 인용에 잘 대처합니다.

spam eggs,"foo,bar","""fizz buzz"""
1.23,4.567,-8.00E+09

이 코드는 유한 상태 머신으로 작성되었으며 한 번에 한 문자 씩 소비합니다. 추론하기가 더 쉽다고 생각합니다.

#include <istream>
#include <string>
#include <vector>

enum class CSVState {
    UnquotedField,
    QuotedField,
    QuotedQuote
};

std::vector<std::string> readCSVRow(const std::string &row) {
    CSVState state = CSVState::UnquotedField;
    std::vector<std::string> fields {""};
    size_t i = 0; // index of the current field
    for (char c : row) {
        switch (state) {
            case CSVState::UnquotedField:
                switch (c) {
                    case ',': // end of field
                              fields.push_back(""); i++;
                              break;
                    case '"': state = CSVState::QuotedField;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedField:
                switch (c) {
                    case '"': state = CSVState::QuotedQuote;
                              break;
                    default:  fields[i].push_back(c);
                              break; }
                break;
            case CSVState::QuotedQuote:
                switch (c) {
                    case ',': // , after closing quote
                              fields.push_back(""); i++;
                              state = CSVState::UnquotedField;
                              break;
                    case '"': // "" -> "
                              fields[i].push_back('"');
                              state = CSVState::QuotedField;
                              break;
                    default:  // end of quote
                              state = CSVState::UnquotedField;
                              break; }
                break;
        }
    }
    return fields;
}

/// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes"""
std::vector<std::vector<std::string>> readCSV(std::istream &in) {
    std::vector<std::vector<std::string>> table;
    std::string row;
    while (!in.eof()) {
        std::getline(in, row);
        if (in.bad() || in.fail()) {
            break;
        }
        auto fields = readCSVRow(row);
        table.push_back(fields);
    }
    return table;
}

6
고마워, 이것이 가장 완전한 대답이라고 생각합니다. 여기에 너무 묻혀 있습니다.
mihai

이 중첩 된 문자열 벡터는 최신 프로세서에게는 전혀 적합하지 않습니다. 캐싱 능력을
버립니다

또한 모든 스위치 설명을 얻었습니다
Nikolaos Giotis

내가 오래된 컴파일러를 사용하고 있기 때문에 최고의 답변이 효과가 없었습니다. 이 답변은 효과가 있으며, 벡터 초기화에는 다음이 필요할 수 있습니다.const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

C ++ 문자열 툴킷 라이브러리 (StrTk는) 당신이 중 하나에서 데이터를로드 할 수있는 토큰 그리드 클래스가 텍스트 파일, 문자열이나 문자 버퍼 행 열 방식으로, 및 구문 분석 / 처리 그들에게 있습니다.

행 구분 기호와 열 구분 기호를 지정하거나 기본값을 사용할 수 있습니다.

void foo()
{
   std::string data = "1,2,3,4,5\n"
                      "0,2,4,6,8\n"
                      "1,3,5,7,9\n";

   strtk::token_grid grid(data,data.size(),",");

   for(std::size_t i = 0; i < grid.row_count(); ++i)
   {
      strtk::token_grid::row_type r = grid.row(i);
      for(std::size_t j = 0; j < r.size(); ++j)
      {
         std::cout << r.get<int>(j) << "\t";
      }
      std::cout << std::endl;
   }
   std::cout << std::endl;
}

더 많은 예제는 여기 에서 찾을 수 있습니다


1
하지만 strtk 지원이 필드를 doublequoted , 심지어 (를 통해 주변의 따옴표를 제거 options.trim_dquotes = true),은 (예를 들어 필드 배 쌍 따옴표를 제거를 지원하지 않습니다 "She said ""oh no"", and left."는 C - 문자열로 "She said \"oh no\", and left."). 직접해야합니다.
rampion

1
를 사용할 때 strtk줄 바꿈 문자가 포함 된 큰 따옴표로 묶인 필드를 수동으로 처리해야합니다.
rampion

29

escaped_list_separator와 함께 Boost Tokenizer를 사용할 수 있습니다.

escaped_list_separator 는 csv의 상위 집합을 구문 분석합니다. 부스트 :: 토큰 화기

부스트 토크 나이저 헤더 파일 만 사용하며 부스트 라이브러리에 대한 링크는 필요하지 않습니다.

다음은 예입니다 (자세한 내용은 C ++ 에서 Boost Tokenizer로 CSV 파일 구문 분석 또는 참조 Boost::tokenizer).

#include <iostream>     // cout, endl
#include <fstream>      // fstream
#include <vector>
#include <string>
#include <algorithm>    // copy
#include <iterator>     // ostream_operator
#include <boost/tokenizer.hpp>

int main()
{
    using namespace std;
    using namespace boost;
    string data("data.csv");

    ifstream in(data.c_str());
    if (!in.is_open()) return 1;

    typedef tokenizer< escaped_list_separator<char> > Tokenizer;
    vector< string > vec;
    string line;

    while (getline(in,line))
    {
        Tokenizer tok(line);
        vec.assign(tok.begin(),tok.end());

        // vector now contains strings from one row, output to cout here
        copy(vec.begin(), vec.end(), ostream_iterator<string>(cout, "|"));

        cout << "\n----------------------" << endl;
    }
}

그리고 포함 된 줄 바꿈 mybyteofcode.blogspot.com/2010/11/… 을 구문 분석 하려면 .
stefanB

이 기술이 작동하는 동안 성능이 매우 떨어지는 것으로 나타났습니다. 줄당 10 개의 필드가있는 90000 개의 라인 CSV 파일을 구문 분석하는 데 2GHz Xeon에서 약 8 초가 걸립니다. Python Standard Library csv 모듈은 약 0.3 초 ​​안에 동일한 파일을 구문 분석합니다.
Rob Smallshire

@Rob 흥미 롭습니다-Python CSV는 어떻게 다른가요?
tofutim

1
@RobSmallshire 고성능 코드가 아닌 간단한 예제 코드입니다. 이 코드는 한 줄에 모든 필드를 복사합니다. 성능을 높이려면 다른 옵션을 사용하고 복사하는 대신 버퍼의 필드에 대한 참조 만 반환합니다.
stefanB

29

CSV를 구문 분석하는 데 Spirit을 사용하는 것은 과도하지 않습니다. Spirit은 마이크로 파싱 작업에 적합합니다. 예를 들어 Spirit 2.1을 사용하면 다음과 같이 쉽습니다.

bool r = phrase_parse(first, last,

    //  Begin grammar
    (
        double_ % ','
    )
    ,
    //  End grammar

    space, v);

벡터 v는 값으로 채워집니다. 일련의 튜토리얼이 있습니다Boost 1.41과 함께 출시 된 새로운 Spirit 2.1 문서 .

튜토리얼은 단순에서 복합으로 진행됩니다. CSV 파서는 가운데 어딘가에 표시되며 Spirit 사용에 대한 다양한 기술을 다룹니다. 생성 된 코드는 직접 작성한 코드만큼 빡빡합니다. 생성 된 어셈블러를 확인하십시오!


18
실제로 너무 많은 시간이 소요되고 컴파일 시간이 길어지고 간단한 "마이크로 파싱 작업"에 Spirit을 사용하는 것은 부당합니다.
Gerdiner

13
또한 위의 코드가 CSV를 구문 분석하지 않고 쉼표로 구분 된 벡터 유형의 범위를 구문 분석한다는 점을 지적하고 싶습니다. 따옴표, 다양한 유형의 열 등을 처리하지 않습니다. 질문에 전혀 답하지 않는 것에 대한 짧은 19 투표는 나에게 조금 의심스러워 보입니다.
Gerdiner

9
@ Gerdiner Nonsense. 작은 파서의 컴파일 시간은 그렇게 크지는 않지만 코드를 자체 컴파일 단위에 넣고 한 번 컴파일하기 때문에 관련이 없습니다 . 그런 다음 연결하기 만하면되는만큼 효율적입니다. 그리고 다른 의견으로는 프로세서만큼 CSV의 방언이 있습니다. 이것은 확실히 매우 유용한 방언은 아니지만 인용 된 값을 처리하기 위해 사소하게 확장 될 수 있습니다.
Konrad Rudolph

11
@konrad : 주 파일 만있는 빈 파일에 "#include <boost / spirit / include / qi.hpp>"를 포함 시키면 MSVC 2012에서는 2.ghz에서 실행되는 corei7에서 9.7 초가 걸리지 않습니다. 불필요한 팽창입니다. 허용 된 답변은 동일한 컴퓨터에서 2 초 미만으로 컴파일되며, '적절한'Boost.Spirit 예제가 컴파일하는 데 걸리는 시간을 상상하고 싶지 않습니다.
Gerdiner

11
@ Gerdiner cvs 처리가 너무 큰 것처럼 간단한 것에 정신을 사용하는 데 드는 오버 헤드에 동의해야합니다.

18

당신이 경우 DO 제대로 CSV 구문 분석에 대한주의를,이 그것을 할 것입니다 ... 비교적 천천히 한 번에 하나 개의 문자를 작동한다.

 void ParseCSV(const string& csvSource, vector<vector<string> >& lines)
    {
       bool inQuote(false);
       bool newLine(false);
       string field;
       lines.clear();
       vector<string> line;

       string::const_iterator aChar = csvSource.begin();
       while (aChar != csvSource.end())
       {
          switch (*aChar)
          {
          case '"':
             newLine = false;
             inQuote = !inQuote;
             break;

          case ',':
             newLine = false;
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                line.push_back(field);
                field.clear();
             }
             break;

          case '\n':
          case '\r':
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                if (newLine == false)
                {
                   line.push_back(field);
                   lines.push_back(line);
                   field.clear();
                   line.clear();
                   newLine = true;
                }
             }
             break;

          default:
             newLine = false;
             field.push_back(*aChar);
             break;
          }

          aChar++;
       }

       if (field.size())
          line.push_back(field);

       if (line.size())
          lines.push_back(line);
    }

AFAICT 포함 된 따옴표를 올바르게 처리하지 않습니다 (예 : "이 문자열에는" "내장 된 따옴표" "" ","foo ", 1)
Jeremy Friesner

14

CSV 파일에 Boost Tokenizer escaped_list_separator를 사용하는 경우 다음을 알고 있어야합니다.

  1. 이스케이프 문자가 필요합니다 (기본 백 슬래시-\).
  2. 스플리터 / 세퍼레이터 문자가 필요합니다 (기본 쉼표-,)
  3. 따옴표 문자가 필요합니다 (기본 따옴표- ").

Wiki에서 지정한 CSV 형식은 데이터 필드에 따옴표로 구분 기호를 포함 할 수 있음을 나타냅니다 (지원됨).

1997, Ford, E350, "슈퍼 럭셔리 트럭"

위키에서 지정한 CSV 형식은 작은 따옴표를 큰 따옴표로 처리해야한다고 말합니다 (escaped_list_separator는 모든 따옴표 문자를 제거합니다).

1997 년, 포드, E350, "슈퍼" "고급스러운" "트럭"

CSV 형식은 백 슬래시 문자를 제거하도록 지정하지 않습니다 (escaped_list_separator는 모든 이스케이프 문자를 제거합니다).

boost escaped_list_separator의 기본 동작을 수정하기위한 가능한 해결 방법 :

  1. 먼저 모든 백 슬래시 문자 (\)를 두 개의 백 슬래시 문자 (\\)로 바꾸십시오.
  2. 두 번째 인용 부호 ( "")를 하나의 백 슬래시 문자와 따옴표 (\ ")로 바꾸십시오.

이 해결 방법에는 큰 따옴표로 표시되는 빈 데이터 필드가 작은 따옴표로 변환되는 부작용이 있습니다. 토큰을 반복 할 때 토큰이 작은 따옴표인지 확인하고 빈 문자열처럼 취급해야합니다.

인용 부호 안에 줄 바꿈이 없으면 오래되지 않습니다.


8

C ++로 작성된 CSV 스트림 편집기 인 내 FOSS 프로젝트 CSVfix ( 업데이트 된 링크 ) 를보고 싶을 수도 있습니다 . CSV 파서는 아무런 상이 없지만 작업을 수행하며 전체 패키지는 코드를 작성하지 않고도 필요한 것을 수행 할 수 있습니다.

참조 alib / SRC / a_csv.cpp csv로 파서 및 csvlib / SRC / csved_ioman.cpp ( IOManager::ReadCSV)를 사용, 예를 들어.


대단해 ... 베타 / 생산 상태는 어떻습니까?
neuro

버전 번호로 제안 된 상태는 "개발 중"입니다. 버전 1.0으로 가기 전에 사용자의 피드백이 더 필요합니다. 또한 CSV의 XML 프로덕션과 관련하여 추가하고 싶은 기능이 몇 가지 더 있습니다.

그것을 즐겨 찾기에 추가하고 멋진 표준 CSV 파일을 다루어야 할 때 다음에 시도해 볼 것입니다 ...
neuro

8

모든 CSV 질문이 여기로 리디렉션되는 것처럼 보이므로 여기에 답변을 게시 할 것이라고 생각했습니다. 이 답변은 asker의 질문을 직접 다루지는 않습니다. CSV 형식으로 알려진 스트림에서 읽을 수 있기를 원했으며 각 필드의 유형도 이미 알려져 있습니다. 물론, 아래의 방법을 사용하여 모든 필드를 문자열 유형으로 취급 할 수 있습니다.

CSV 입력 스트림을 사용하고 싶었던 방법의 예로, 다음 입력을 고려하십시오 ( CSV의 Wikipedia 페이지에서 가져옴 ).

const char input[] =
"Year,Make,Model,Description,Price\n"
"1997,Ford,E350,\"ac, abs, moon\",3000.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition\"\"\",\"\",4900.00\n"
"1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",\"\",5000.00\n"
"1996,Jeep,Grand Cherokee,\"MUST SELL!\n\
air, moon roof, loaded\",4799.00\n"
;

그런 다음 다음과 같은 데이터를 읽을 수 있기를 원했습니다.

std::istringstream ss(input);
std::string title[5];
int year;
std::string make, model, desc;
float price;
csv_istream(ss)
    >> title[0] >> title[1] >> title[2] >> title[3] >> title[4];
while (csv_istream(ss)
       >> year >> make >> model >> desc >> price) {
    //...do something with the record...
}

이것이 내가 끝내는 해결책이었습니다.

struct csv_istream {
    std::istream &is_;
    csv_istream (std::istream &is) : is_(is) {}
    void scan_ws () const {
        while (is_.good()) {
            int c = is_.peek();
            if (c != ' ' && c != '\t') break;
            is_.get();
        }
    }
    void scan (std::string *s = 0) const {
        std::string ws;
        int c = is_.get();
        if (is_.good()) {
            do {
                if (c == ',' || c == '\n') break;
                if (s) {
                    ws += c;
                    if (c != ' ' && c != '\t') {
                        *s += ws;
                        ws.clear();
                    }
                }
                c = is_.get();
            } while (is_.good());
            if (is_.eof()) is_.clear();
        }
    }
    template <typename T, bool> struct set_value {
        void operator () (std::string in, T &v) const {
            std::istringstream(in) >> v;
        }
    };
    template <typename T> struct set_value<T, true> {
        template <bool SIGNED> void convert (std::string in, T &v) const {
            if (SIGNED) v = ::strtoll(in.c_str(), 0, 0);
            else v = ::strtoull(in.c_str(), 0, 0);
        }
        void operator () (std::string in, T &v) const {
            convert<is_signed_int<T>::val>(in, v);
        }
    };
    template <typename T> const csv_istream & operator >> (T &v) const {
        std::string tmp;
        scan(&tmp);
        set_value<T, is_int<T>::val>()(tmp, v);
        return *this;
    }
    const csv_istream & operator >> (std::string &v) const {
        v.clear();
        scan_ws();
        if (is_.peek() != '"') scan(&v);
        else {
            std::string tmp;
            is_.get();
            std::getline(is_, tmp, '"');
            while (is_.peek() == '"') {
                v += tmp;
                v += is_.get();
                std::getline(is_, tmp, '"');
            }
            v += tmp;
            scan();
        }
        return *this;
    }
    template <typename T>
    const csv_istream & operator >> (T &(*manip)(T &)) const {
        is_ >> manip;
        return *this;
    }
    operator bool () const { return !is_.fail(); }
};

C ++ 11의 새로운 필수 특성 템플릿으로 단순화 할 수있는 다음과 같은 도우미가 있습니다.

template <typename T> struct is_signed_int { enum { val = false }; };
template <> struct is_signed_int<short> { enum { val = true}; };
template <> struct is_signed_int<int> { enum { val = true}; };
template <> struct is_signed_int<long> { enum { val = true}; };
template <> struct is_signed_int<long long> { enum { val = true}; };

template <typename T> struct is_unsigned_int { enum { val = false }; };
template <> struct is_unsigned_int<unsigned short> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned int> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long long> { enum { val = true}; };

template <typename T> struct is_int {
    enum { val = (is_signed_int<T>::val || is_unsigned_int<T>::val) };
};

온라인으로 사용해보십시오!


6

헤더 전용 C ++ 11 CSV 파서를 작성했습니다 . 잘 테스트되고 빠르며 전체 CSV 사양 (인용 된 필드, 따옴표로 묶은 구분자 / 종료 자, 따옴표 이스케이프 등)을 지원하며 사양을 준수하지 않는 CSV를 설명하도록 구성 할 수 있습니다.

유창한 인터페이스를 통해 구성이 수행됩니다.

// constructor accepts any input stream
CsvParser parser = CsvParser(std::cin)
  .delimiter(';')    // delimited by ; instead of ,
  .quote('\'')       // quoted fields use ' instead of "
  .terminator('\0'); // terminated by \0 instead of by \r\n, \n, or \r

구문 분석은 루프 기반의 범위 일뿐입니다.

#include <iostream>
#include "../parser.hpp"

using namespace aria::csv;

int main() {
  std::ifstream f("some_file.csv");
  CsvParser parser(f);

  for (auto& row : parser) {
    for (auto& field : row) {
      std::cout << field << " | ";
    }
    std::cout << std::endl;
  }
}

1
훌륭한 작업이지만 세 가지를 더 추가해야합니다. (1) 헤더 읽기 (2) 이름으로 필드 인덱싱 제공 (3) 동일한 문자열 벡터를 재사용하여 루프에서 메모리를 재할
당하지 않음

@MaksymGanenko 나는 # 3을한다. # 2에 대해 자세히 설명해 주시겠습니까?
m0meni

1
필드를 행 단위가 아니라 헤더 (CSV 테이블의 첫 번째 행)에 지정된 이름으로 가져 오는 것이 매우 유용합니다. 예를 들어, "날짜"필드가 포함 된 CSV 테이블이 필요하지만 "날짜"필드 인덱스가 무엇인지 잘 모르겠습니다.
Maksym Ganenko

1
@MaksymGanenko 아 무슨 말인지 알 겠어. 있다 github.com/ben-strasser/fast-cpp-csv-parser은 당신이 컴파일시에 당신의 CSV의 열을 알고있는 경우를 위해, 그리고 나보다 아마 더 낫다. 내가 원하는 것은 많은 다른 CSV에 동일한 코드를 사용하고 미리 어떻게 생겼는지 모르는 경우에 대한 CSV 파서였습니다. 따라서 아마도 # 2를 추가하지는 않지만 나중에 언젠가는 # 1을 추가 할 것입니다.
m0meni

5

다른 CSV I / O 라이브러리는 여기에서 찾을 수 있습니다.

http://code.google.com/p/fast-cpp-csv-parser/

#include "csv.h"

int main(){
  io::CSVReader<3> in("ram.csv");
  in.read_header(io::ignore_extra_column, "vendor", "size", "speed");
  std::string vendor; int size; double speed;
  while(in.read_row(vendor, size, speed)){
    // do stuff with the data
  }
}

2
좋지만 컴파일 타임에 열 수를 선택해야합니다. 많은 응용 프로그램에별로 유용하지 않습니다.
quant_dev

5

C ++ 11 의 Loki Astari의 답변 과 비슷한 또 다른 솔루션 . 여기 std::tuple의 행 은 지정된 유형입니다. 이 코드는 한 줄을 스캔 한 다음 각 구분 기호까지 스캔 한 다음 값을 템플릿 코드와 함께 튜플로 직접 변환하고 덤프합니다.

for (auto row : csv<std::string, int, float>(file, ',')) {
    std::cout << "first col: " << std::get<0>(row) << std::endl;
}

전진 :

  • C ++ 11 만 사용하면 매우 깨끗하고 사용하기 쉽습니다.
  • std::tuple<t1, ...>via 로 자동 유형 변환 operator>>.

빠진 것 :

  • 탈출과 인용
  • 잘못된 CSV의 경우 오류 처리가 없습니다.

주요 코드 :

#include <iterator>
#include <sstream>
#include <string>

namespace csvtools {
    /// Read the last element of the tuple without calling recursively
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx >= std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
    }

    /// Read the @p idx-th element of the tuple and then calls itself with @p idx + 1 to
    /// read the next element of the tuple. Automatically falls in the previous case when
    /// reaches the last element of the tuple thanks to enable_if
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx < std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
        read_tuple<idx + 1, fields...>(in, out, delimiter);
    }
}

/// Iterable csv wrapper around a stream. @p fields the list of types that form up a row.
template <class... fields>
class csv {
    std::istream &_in;
    const char _delim;
public:
    typedef std::tuple<fields...> value_type;
    class iterator;

    /// Construct from a stream.
    inline csv(std::istream &in, const char delim) : _in(in), _delim(delim) {}

    /// Status of the underlying stream
    /// @{
    inline bool good() const {
        return _in.good();
    }
    inline const std::istream &underlying_stream() const {
        return _in;
    }
    /// @}

    inline iterator begin();
    inline iterator end();
private:

    /// Reads a line into a stringstream, and then reads the line into a tuple, that is returned
    inline value_type read_row() {
        std::string line;
        std::getline(_in, line);
        std::stringstream line_stream(line);
        std::tuple<fields...> retval;
        csvtools::read_tuple<0, fields...>(line_stream, retval, _delim);
        return retval;
    }
};

/// Iterator; just calls recursively @ref csv::read_row and stores the result.
template <class... fields>
class csv<fields...>::iterator {
    csv::value_type _row;
    csv *_parent;
public:
    typedef std::input_iterator_tag iterator_category;
    typedef csv::value_type         value_type;
    typedef std::size_t             difference_type;
    typedef csv::value_type *       pointer;
    typedef csv::value_type &       reference;

    /// Construct an empty/end iterator
    inline iterator() : _parent(nullptr) {}
    /// Construct an iterator at the beginning of the @p parent csv object.
    inline iterator(csv &parent) : _parent(parent.good() ? &parent : nullptr) {
        ++(*this);
    }

    /// Read one row, if possible. Set to end if parent is not good anymore.
    inline iterator &operator++() {
        if (_parent != nullptr) {
            _row = _parent->read_row();
            if (!_parent->good()) {
                _parent = nullptr;
            }
        }
        return *this;
    }

    inline iterator operator++(int) {
        iterator copy = *this;
        ++(*this);
        return copy;
    }

    inline csv::value_type const &operator*() const {
        return _row;
    }

    inline csv::value_type const *operator->() const {
        return &_row;
    }

    bool operator==(iterator const &other) {
        return (this == &other) or (_parent == nullptr and other._parent == nullptr);
    }
    bool operator!=(iterator const &other) {
        return not (*this == other);
    }
};

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::begin() {
    return iterator(*this);
}

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::end() {
    return iterator();
}

나는 GitHub 에 작은 작업 예제를 넣었다 ; 나는 숫자 데이터를 파싱하는 데 사용했으며 그 목적을 달성했습니다.


1
대부분의 컴파일러가 자체적으로 결정하므로 인라인에 신경 쓰지 않아도됩니다. 적어도 Visual C ++에서는 확실합니다. 메소드 스펙과 독립적으로 메소드를 인라인 할 수 있습니다.
MrPisarik

1
그것이 바로 내가 명시 적으로 표시 한 이유입니다. 내가 주로 사용하는 Gcc와 Clang은 자체 규칙을 가지고 있습니다. "인라인"키워드는 인센티브 여야합니다.
Spak

4

다음은 유니 코드 CSV 파서의 또 다른 구현입니다 (wchar_t와 함께 작동). Jonathan Leffler가 나머지를 썼을 때 나는 그 일부를 썼습니다.

참고 :이 파서는 특히 손상되거나 형식이 잘못된 CSV 파일을 가져올 때 Excel의 동작을 최대한 가깝게 복제하기위한 것 입니다.

이것은 원래 질문입니다-여러 줄 필드와 이스케이프 된 큰 따옴표로 CSV 파일 구문 분석

이 코드는 SSCCE (짧은, 자체 포함, 올바른 예) 코드입니다.

#include <stdbool.h>
#include <wchar.h>
#include <wctype.h>

extern const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline);

// Returns a pointer to the start of the next field,
// or zero if this is the last field in the CSV
// p is the start position of the field
// sep is the separator used, i.e. comma or semicolon
// newline says whether the field ends with a newline or with a comma
const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline)
{
    // Parse quoted sequences
    if ('"' == p[0]) {
        p++;
        while (1) {
            // Find next double-quote
            p = wcschr(p, L'"');
            // If we don't find it or it's the last symbol
            // then this is the last field
            if (!p || !p[1])
                return 0;
            // Check for "", it is an escaped double-quote
            if (p[1] != '"')
                break;
            // Skip the escaped double-quote
            p += 2;
        }
    }

    // Find next newline or comma.
    wchar_t newline_or_sep[4] = L"\n\r ";
    newline_or_sep[2] = sep;
    p = wcspbrk(p, newline_or_sep);

    // If no newline or separator, this is the last field.
    if (!p)
        return 0;

    // Check if we had newline.
    *newline = (p[0] == '\r' || p[0] == '\n');

    // Handle "\r\n", otherwise just increment
    if (p[0] == '\r' && p[1] == '\n')
        p += 2;
    else
        p++;

    return p;
}

static wchar_t *csvFieldData(const wchar_t *fld_s, const wchar_t *fld_e, wchar_t *buffer, size_t buflen)
{
    wchar_t *dst = buffer;
    wchar_t *end = buffer + buflen - 1;
    const wchar_t *src = fld_s;

    if (*src == L'"')
    {
        const wchar_t *p = src + 1;
        while (p < fld_e && dst < end)
        {
            if (p[0] == L'"' && p+1 < fld_s && p[1] == L'"')
            {
                *dst++ = p[0];
                p += 2;
            }
            else if (p[0] == L'"')
            {
                p++;
                break;
            }
            else
                *dst++ = *p++;
        }
        src = p;
    }
    while (src < fld_e && dst < end)
        *dst++ = *src++;
    if (dst >= end)
        return 0;
    *dst = L'\0';
    return(buffer);
}

static void dissect(const wchar_t *line)
{
    const wchar_t *start = line;
    const wchar_t *next;
    bool     eol;
    wprintf(L"Input %3zd: [%.*ls]\n", wcslen(line), wcslen(line)-1, line);
    while ((next = nextCsvField(start, L',', &eol)) != 0)
    {
        wchar_t buffer[1024];
        wprintf(L"Raw Field: [%.*ls] (eol = %d)\n", (next - start - eol), start, eol);
        if (csvFieldData(start, next-1, buffer, sizeof(buffer)/sizeof(buffer[0])) != 0)
            wprintf(L"Field %3zd: [%ls]\n", wcslen(buffer), buffer);
        start = next;
    }
}

static const wchar_t multiline[] =
   L"First field of first row,\"This field is multiline\n"
    "\n"
    "but that's OK because it's enclosed in double quotes, and this\n"
    "is an escaped \"\" double quote\" but this one \"\" is not\n"
    "   \"This is second field of second row, but it is not multiline\n"
    "   because it doesn't start \n"
    "   with an immediate double quote\"\n"
    ;

int main(void)
{
    wchar_t line[1024];

    while (fgetws(line, sizeof(line)/sizeof(line[0]), stdin))
        dissect(line);
    dissect(multiline);

    return 0;
}

3

CSV 파일을 구문 분석하기 위해 사용하기 쉬운 C ++ 라이브러리가 필요했지만 사용 가능한 파일을 찾을 수 없으므로 결국 하나를 작성했습니다. Rapidcsv 는 C ++ 11 헤더 전용 라이브러리로, 파싱 된 열 (또는 행)에 선택한 데이터 유형의 벡터로 직접 액세스 할 수 있습니다. 예를 들면 다음과 같습니다.

#include <iostream>
#include <vector>
#include <rapidcsv.h>

int main()
{
  rapidcsv::Document doc("../tests/msft.csv");

  std::vector<float> close = doc.GetColumn<float>("Close");
  std::cout << "Read " << close.size() << " values." << std::endl;
}

1
훌륭하지만 헤더에 빈 레이블이 있으면 라이브러리가 제대로 작동하지 않습니다. 이는 Excel / LibreOffice NxN 테이블에서 일반적입니다. 또한 마지막 데이터 줄을 건너 뛸 수 있습니다. 불행히도, 귀하의 라이브러리는 강력하지 않습니다.
Maksym Ganenko

1
피드백 @MaksymGanenko에게 감사합니다. 마지막 줄 바꿈없이 마지막 줄에 대한 "마지막 데이터 줄"버그를 수정했습니다. 언급 된 다른 문제- "빈 레이블이있는 헤더"-그것이 무엇을 의미하는지 잘 모르겠습니까? 라이브러리는 빈 레이블 (인용 및 인용되지 않은)을 처리해야합니다. 헤더 행 / 열없이 CSV를 읽을 수도 있지만 사용자가이를 지정해야합니다 (콜 제목 id -1 및 행 제목 id -1). 지원되는 특정 사용 사례가있는 경우 GitHub 페이지에서 자세한 내용을 제공하거나 버그를보고하십시오. 감사!
d99kris

2

실례 합니다만,이 모든 것은 몇 줄의 코드를 숨기는 복잡한 구문처럼 보입니다.

왜 그렇지 않습니까?

/**

  Read line from a CSV file

  @param[in] fp file pointer to open file
  @param[in] vls reference to vector of strings to hold next line

  */
void readCSV( FILE *fp, std::vector<std::string>& vls )
{
    vls.clear();
    if( ! fp )
        return;
    char buf[10000];
    if( ! fgets( buf,999,fp) )
        return;
    std::string s = buf;
    int p,q;
    q = -1;
    // loop over columns
    while( 1 ) {
        p = q;
        q = s.find_first_of(",\n",p+1);
        if( q == -1 ) 
            break;
        vls.push_back( s.substr(p+1,q-p-1) );
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<std::string> vls;
    FILE * fp = fopen( argv[1], "r" );
    if( ! fp )
        return 1;
    readCSV( fp, vls );
    readCSV( fp, vls );
    readCSV( fp, vls );
    std::cout << "row 3, col 4 is " << vls[3].c_str() << "\n";

    return 0;
}

음, 왜 ",\n"문자열에 있을까요?
Timmmm

@Timmmm은 String 클래스의 substr 메소드를 찾아 보면 여러 문자가 필요하다는 것을 알 수 있습니다. \ n은 개행 문자 이므로이 경우 단일 문자로 계산됩니다. 전체 값을 전체적으로 검색하지는 않습니다. 각 개별 캐릭터를 검색합니다. 즉 쉼표 또는 줄 바꿈. substr은 찾은 첫 번째 문자의 위치를 ​​리턴하고 둘 중 하나를 찾지 못하면 -1을 리턴하므로 행 읽기를 완료했습니다. fp는 파일의 내부 위치를 내부적으로 추적하므로 readCSV를 호출 할 때마다 한 번에 한 행씩 이동합니다.
Martyn Shutt

2

행렬을 읽는 코드는 다음과 같습니다. matlab에도 csvwrite 함수가 있습니다.

void loadFromCSV( const std::string& filename )
{
    std::ifstream       file( filename.c_str() );
    std::vector< std::vector<std::string> >   matrix;
    std::vector<std::string>   row;
    std::string                line;
    std::string                cell;

    while( file )
    {
        std::getline(file,line);
        std::stringstream lineStream(line);
        row.clear();

        while( std::getline( lineStream, cell, ',' ) )
            row.push_back( cell );

        if( !row.empty() )
            matrix.push_back( row );
    }

    for( int i=0; i<int(matrix.size()); i++ )
    {
        for( int j=0; j<int(matrix[i].size()); j++ )
            std::cout << matrix[i][j] << " ";

        std::cout << std::endl;
    }
}

2

fopen, fscanf 함수를 사용하여 .csv 파일을 열고 읽을 수 있지만 중요한 것은 데이터를 구문 분석하는 것입니다. 구분 기호를 사용하여 데이터를 구문 분석하는 가장 간단한 방법입니다. .csv의 경우 구분 기호는 ','입니다.

data1.csv 파일이 다음과 같다고 가정하십시오.

A,45,76,01
B,77,67,02
C,63,76,03
D,65,44,04

데이터를 토큰 화하고 char 배열에 저장할 수 있으며 나중에 적절한 변환을 위해 atoi () 등의 함수를 사용할 수 있습니다

FILE *fp;
char str1[10], str2[10], str3[10], str4[10];

fp = fopen("G:\\data1.csv", "r");
if(NULL == fp)
{
    printf("\nError in opening file.");
    return 0;
}
while(EOF != fscanf(fp, " %[^,], %[^,], %[^,], %s, %s, %s, %s ", str1, str2, str3, str4))
{
    printf("\n%s %s %s %s", str1, str2, str3, str4);
}
fclose(fp);

[^,], ^ -it은 논리를 반전시킵니다. 즉, 쉼표를 포함하지 않고 마지막 문자열을 포함하는 모든 문자열과 일치한다는 의미입니다. 이전 문자열을 종료 한 쉼표와 일치합니다.


2

가장 먼저해야 할 일은 파일이 존재하는지 확인하는 것입니다. 이를 위해서는 경로에서 파일 스트림을 열고 시도하면됩니다. 파일 스트림을 연 후 stream.fail ()을 사용하여 예상대로 작동하는지 확인하십시오.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

if (test.fail())
{
    test.close();
    return false;
}
else
{
    test.close();
    return true;
}
}

제공된 파일이 올바른 유형의 파일인지 확인해야합니다. 이를 위해서는 파일 확장자를 찾을 때까지 제공된 파일 경로를 살펴 봐야합니다. 파일 확장자가 있으면 .csv 파일인지 확인하십시오.

bool verifyExtension(string filename)
{
int period = 0;

for (unsigned int i = 0; i < filename.length(); i++)
{
    if (filename[i] == '.')
        period = i;
}

string extension;

for (unsigned int i = period; i < filename.length(); i++)
    extension += filename[i];

if (extension == ".csv")
    return true;
else
    return false;
}

이 함수는 나중에 오류 메시지에서 사용되는 파일 확장자를 반환합니다.

string getExtension(string filename)
{
int period = 0;

for (unsigned int i = 0; i < filename.length(); i++)
{
    if (filename[i] == '.')
        period = i;
}

string extension;

if (period != 0)
{
    for (unsigned int i = period; i < filename.length(); i++)
        extension += filename[i];
}
else
    extension = "NO FILE";

return extension;
}

이 함수는 실제로 위에서 만든 오류 검사를 호출 한 다음 파일을 통해 구문 분석합니다.

void parseFile(string fileName)
{
    if (fileExists(fileName) && verifyExtension(fileName))
    {
        ifstream fs;
        fs.open(fileName.c_str());
        string fileCommand;

        while (fs.good())
        {
            string temp;

            getline(fs, fileCommand, '\n');

            for (unsigned int i = 0; i < fileCommand.length(); i++)
            {
                if (fileCommand[i] != ',')
                    temp += fileCommand[i];
                else
                    temp += " ";
            }

            if (temp != "\0")
            {
                // Place your code here to run the file.
            }
        }
        fs.close();
    }
    else if (!fileExists(fileName))
    {
        cout << "Error: The provided file does not exist: " << fileName << endl;

        if (!verifyExtension(fileName))
        {
            if (getExtension(fileName) != "NO FILE")
                cout << "\tCheck the file extension." << endl;
            else
                cout << "\tThere is no file in the provided path." << endl;
        }
    }
    else if (!verifyExtension(fileName)) 
    {
        if (getExtension(fileName) != "NO FILE")
            cout << "Incorrect file extension provided: " << getExtension(fileName) << endl;
        else
            cout << "There is no file in the following path: " << fileName << endl;
    }
}

2

너무 아름다운 것을 사용할 때 자랑스러워해야합니다 boost::spirit

여기 에이 링크의 CSV 사양을 준수하는 파서 (거의) 시도 CSV 사양 가 있습니다 (필드 내에서 줄 바꿈이 필요하지 않았습니다. 또한 쉼표 주위의 공백은 무시됩니다).

이 코드를 컴파일하기 위해 10 초 동안 기다리는 충격적인 경험을 극복 한 후에는 앉아서 즐길 수 있습니다.

// csvparser.cpp
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>

#include <iostream>
#include <string>

namespace qi = boost::spirit::qi;
namespace bascii = boost::spirit::ascii;

template <typename Iterator>
struct csv_parser : qi::grammar<Iterator, std::vector<std::string>(), 
    bascii::space_type>
{
    qi::rule<Iterator, char()                                           > COMMA;
    qi::rule<Iterator, char()                                           > DDQUOTE;
    qi::rule<Iterator, std::string(),               bascii::space_type  > non_escaped;
    qi::rule<Iterator, std::string(),               bascii::space_type  > escaped;
    qi::rule<Iterator, std::string(),               bascii::space_type  > field;
    qi::rule<Iterator, std::vector<std::string>(),  bascii::space_type  > start;

    csv_parser() : csv_parser::base_type(start)
    {
        using namespace qi;
        using qi::lit;
        using qi::lexeme;
        using bascii::char_;

        start       = field % ',';
        field       = escaped | non_escaped;
        escaped     = lexeme['"' >> *( char_ -(char_('"') | ',') | COMMA | DDQUOTE)  >> '"'];
        non_escaped = lexeme[       *( char_ -(char_('"') | ',')                  )        ];
        DDQUOTE     = lit("\"\"")       [_val = '"'];
        COMMA       = lit(",")          [_val = ','];
    }

};

int main()
{
    std::cout << "Enter CSV lines [empty] to quit\n";

    using bascii::space;
    typedef std::string::const_iterator iterator_type;
    typedef csv_parser<iterator_type> csv_parser;

    csv_parser grammar;
    std::string str;
    int fid;
    while (getline(std::cin, str))
    {
        fid = 0;

        if (str.empty())
            break;

        std::vector<std::string> csv;
        std::string::const_iterator it_beg = str.begin();
        std::string::const_iterator it_end = str.end();
        bool r = phrase_parse(it_beg, it_end, grammar, space, csv);

        if (r && it_beg == it_end)
        {
            std::cout << "Parsing succeeded\n";
            for (auto& field: csv)
            {
                std::cout << "field " << ++fid << ": " << field << std::endl;
            }
        }
        else
        {
            std::cout << "Parsing failed\n";
        }
    }

    return 0;
}

엮다:

make csvparser

테스트 ( Wikipedia 에서 도난당한 예 ) :

./csvparser
Enter CSV lines [empty] to quit

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
Parsing succeeded
field 1: 1999
field 2: Chevy
field 3: Venture "Extended Edition, Very Large"
field 4: 
field 5: 5000.00

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00"
Parsing failed

2

이 솔루션은이 4 가지 경우를 감지합니다.

완전한 수업은

https://github.com/pedro-vicente/csv-parser

1,field 2,field 3,
1,field 2,"field 3 quoted, with separator",
1,field 2,"field 3
with newline",
1,field 2,"field 3
with newline and separator,",

파일을 문자별로 읽고 한 번에 한 행씩 벡터 (문자열)를 읽으므로 매우 큰 파일에 적합합니다.

사용법

빈 행이 리턴 될 때까지 (파일 끝) 반복하십시오. 행은 각 항목이 CSV 열인 벡터입니다.

read_csv_t csv;
csv.open("../test.csv");
std::vector<std::string> row;
while (true)
{
  row = csv.read_row();
  if (row.size() == 0)
  {
    break;
  }
}

클래스 선언

class read_csv_t
{
public:
  read_csv_t();
  int open(const std::string &file_name);
  std::vector<std::string> read_row();
private:
  std::ifstream m_ifs;
};

구현

std::vector<std::string> read_csv_t::read_row()
{
  bool quote_mode = false;
  std::vector<std::string> row;
  std::string column;
  char c;
  while (m_ifs.get(c))
  {
    switch (c)
    {
      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //separator ',' detected. 
      //in quote mode add character to column
      //push column if not in quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case ',':
      if (quote_mode == true)
      {
        column += c;
      }
      else
      {
        row.push_back(column);
        column.clear();
      }
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //quote '"' detected. 
      //toggle quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case '"':
      quote_mode = !quote_mode;
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //line end detected
      //in quote mode add character to column
      //return row if not in quote mode
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    case '\n':
    case '\r':
      if (quote_mode == true)
      {
        column += c;
      }
      else
      {
        return row;
      }
      break;

      /////////////////////////////////////////////////////////////////////////////////////////////////////
      //default, add character to column
      /////////////////////////////////////////////////////////////////////////////////////////////////////

    default:
      column += c;
      break;
    }
  }

  //return empty vector if end of file detected 
  m_ifs.close();
  std::vector<std::string> v;
  return v;
}

1

Qt라이브러리의 기능을 살펴볼 수도 있습니다 .

정규 표현식을 지원하며 QString 클래스에는 멋진 메소드가 있습니다. split() 문자열을 제공합니다. QStringList 반환, 원래 문자열을 제공된 구분 기호로 분할하여 얻은 문자열 목록. csv 파일이면 충분합니다 ..

주어진 헤더 이름을 가진 열을 얻으려면 다음을 사용하십시오 .C ++ inheritance Qt problem qstring


따옴표로 쉼표를 처리하지 않습니다
Ezee

1

프로젝트에 부스트를 포함시키지 않으려는 경우 (CSS 구문 분석에 사용하려는 경우 상당히 큽니다 ...)

CSV 파싱에 행운이있었습니다.

http://www.zedwood.com/article/112/cpp-csv-parser

인용 된 필드를 처리하지만 인라인 \ n 문자는 처리하지 않습니다 (대부분의 경우 적합 할 것입니다).


1
컴파일러가 필수적이지 않은 모든 것을 제거해서는 안됩니까?
tofutim

1

이것은 오래된 스레드이지만 여전히 검색 결과의 최상위에 있으므로 std :: stringstream 및 Yves Baumes의 간단한 문자열 바꾸기 방법을 사용하여 솔루션을 추가하고 있습니다.

다음 예제는 파일을 한 줄씩 읽고 //로 시작하는 주석 줄을 무시하고 다른 줄을 문자열, int 및 double 조합으로 구문 분석합니다. Stringstream은 구문 분석을 수행하지만 필드가 공백으로 구분되기를 기대하므로 stringreplace를 사용하여 쉼표를 먼저 공백으로 바꿉니다. 탭은 정상적으로 처리하지만 따옴표로 묶인 문자열은 처리하지 않습니다.

잘못되거나 누락 된 입력은 단순히 무시되며 상황에 따라 좋지 않을 수도 있습니다.

#include <string>
#include <sstream>
#include <fstream>

void StringReplace(std::string& str, const std::string& oldStr, const std::string& newStr)
// code by  Yves Baumes
// http://stackoverflow.com/questions/1494399/how-do-i-search-find-and-replace-in-a-standard-string
{
  size_t pos = 0;
  while((pos = str.find(oldStr, pos)) != std::string::npos)
  {
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}

void LoadCSV(std::string &filename) {
   std::ifstream stream(filename);
   std::string in_line;
   std::string Field;
   std::string Chan;
   int ChanType;
   double Scale;
   int Import;
   while (std::getline(stream, in_line)) {
      StringReplace(in_line, ",", " ");
      std::stringstream line(in_line);
      line >> Field >> Chan >> ChanType >> Scale >> Import;
      if (Field.substr(0,2)!="//") {
         // do your stuff 
         // this is CBuilder code for demonstration, sorry
         ShowMessage((String)Field.c_str() + "\n" + Chan.c_str() + "\n" + IntToStr(ChanType) + "\n" +FloatToStr(Scale) + "\n" +IntToStr(Import));
      }
   }
}

1

가치있는 것에 대한 구현은 다음과 같습니다. wstring 입력을 처리하지만 쉽게 문자열로 조정할 수 있습니다. 필드에서 줄 바꿈을 처리하지 않으며 (응용 프로그램을 지원하지 않지만 지원을 추가하는 것이 너무 어렵지 않으므로) RFC에 따라 "\ r \ n"줄 끝을 준수하지 않습니다 (std :: getline), 그러나 공백 트리밍과 큰 따옴표를 올바르게 처리합니다.

using namespace std;

// trim whitespaces around field or double-quotes, remove double-quotes and replace escaped double-quotes (double double-quotes)
wstring trimquote(const wstring& str, const wstring& whitespace, const wchar_t quotChar)
{
    wstring ws;
    wstring::size_type strBegin = str.find_first_not_of(whitespace);
    if (strBegin == wstring::npos)
        return L"";

    wstring::size_type strEnd = str.find_last_not_of(whitespace);
    wstring::size_type strRange = strEnd - strBegin + 1;

    if((str[strBegin] == quotChar) && (str[strEnd] == quotChar))
    {
        ws = str.substr(strBegin+1, strRange-2);
        strBegin = 0;
        while((strEnd = ws.find(quotChar, strBegin)) != wstring::npos)
        {
            ws.erase(strEnd, 1);
            strBegin = strEnd+1;
        }

    }
    else
        ws = str.substr(strBegin, strRange);
    return ws;
}

pair<unsigned, unsigned> nextCSVQuotePair(const wstring& line, const wchar_t quotChar, unsigned ofs = 0)
{
    pair<unsigned, unsigned> r;
    r.first = line.find(quotChar, ofs);
    r.second = wstring::npos;
    if(r.first != wstring::npos)
    {
        r.second = r.first;
        while(((r.second = line.find(quotChar, r.second+1)) != wstring::npos)
            && (line[r.second+1] == quotChar)) // WARNING: assumes null-terminated string such that line[r.second+1] always exist
            r.second++;

    }
    return r;
}

unsigned parseLine(vector<wstring>& fields, const wstring& line)
{
    unsigned ofs, ofs0, np;
    const wchar_t delim = L',';
    const wstring whitespace = L" \t\xa0\x3000\x2000\x2001\x2002\x2003\x2004\x2005\x2006\x2007\x2008\x2009\x200a\x202f\x205f";
    const wchar_t quotChar = L'\"';
    pair<unsigned, unsigned> quot;

    fields.clear();

    ofs = ofs0 = 0;
    quot = nextCSVQuotePair(line, quotChar);
    while((np = line.find(delim, ofs)) != wstring::npos)
    {
        if((np > quot.first) && (np < quot.second))
        { // skip delimiter inside quoted field
            ofs = quot.second+1;
            quot = nextCSVQuotePair(line, quotChar, ofs);
            continue;
        }
        fields.push_back( trimquote(line.substr(ofs0, np-ofs0), whitespace, quotChar) );
        ofs = ofs0 = np+1;
    }
    fields.push_back( trimquote(line.substr(ofs0), whitespace, quotChar) );

    return fields.size();
}

1

필요한 것은 배가 된 데이터 파일 (정수, 텍스트 없음)을로드하는 것이라면 바로 사용할 수있는 기능입니다.

#include <sstream>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

/**
 * Parse a CSV data file and fill the 2d STL vector "data".
 * Limits: only "pure datas" of doubles, not encapsulated by " and without \n inside.
 * Further no formatting in the data (e.g. scientific notation)
 * It however handles both dots and commas as decimal separators and removes thousand separator.
 * 
 * returnCodes[0]: file access 0-> ok 1-> not able to read; 2-> decimal separator equal to comma separator
 * returnCodes[1]: number of records
 * returnCodes[2]: number of fields. -1 If rows have different field size
 * 
 */
vector<int>
readCsvData (vector <vector <double>>& data, const string& filename, const string& delimiter, const string& decseparator){

 int vv[3] = { 0,0,0 };
 vector<int> returnCodes(&vv[0], &vv[0]+3);

 string rowstring, stringtoken;
 double doubletoken;
 int rowcount=0;
 int fieldcount=0;
 data.clear();

 ifstream iFile(filename, ios_base::in);
 if (!iFile.is_open()){
   returnCodes[0] = 1;
   return returnCodes;
 }
 while (getline(iFile, rowstring)) {
    if (rowstring=="") continue; // empty line
    rowcount ++; //let's start with 1
    if(delimiter == decseparator){
      returnCodes[0] = 2;
      return returnCodes;
    }
    if(decseparator != "."){
     // remove dots (used as thousand separators)
     string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), '.');
     rowstring.erase(end_pos, rowstring.end());
     // replace decimal separator with dots.
     replace(rowstring.begin(), rowstring.end(),decseparator.c_str()[0], '.'); 
    } else {
     // remove commas (used as thousand separators)
     string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), ',');
     rowstring.erase(end_pos, rowstring.end());
    }
    // tokenize..
    vector<double> tokens;
    // Skip delimiters at beginning.
    string::size_type lastPos = rowstring.find_first_not_of(delimiter, 0);
    // Find first "non-delimiter".
    string::size_type pos     = rowstring.find_first_of(delimiter, lastPos);
    while (string::npos != pos || string::npos != lastPos){
        // Found a token, convert it to double add it to the vector.
        stringtoken = rowstring.substr(lastPos, pos - lastPos);
        if (stringtoken == "") {
      tokens.push_back(0.0);
    } else {
          istringstream totalSString(stringtoken);
      totalSString >> doubletoken;
      tokens.push_back(doubletoken);
    }     
        // Skip delimiters.  Note the "not_of"
        lastPos = rowstring.find_first_not_of(delimiter, pos);
        // Find next "non-delimiter"
        pos = rowstring.find_first_of(delimiter, lastPos);
    }
    if(rowcount == 1){
      fieldcount = tokens.size();
      returnCodes[2] = tokens.size();
    } else {
      if ( tokens.size() != fieldcount){
    returnCodes[2] = -1;
      }
    }
    data.push_back(tokens);
 }
 iFile.close();
 returnCodes[1] = rowcount;
 return returnCodes;
}

1

또 다른 빠르고 쉬운 방법은 다음을 사용하는 것입니다 Boost.Fusion I/O.

#include <iostream>
#include <sstream>

#include <boost/fusion/adapted/boost_tuple.hpp>
#include <boost/fusion/sequence/io.hpp>

namespace fusion = boost::fusion;

struct CsvString
{
    std::string value;

    // Stop reading a string once a CSV delimeter is encountered.
    friend std::istream& operator>>(std::istream& s, CsvString& v) {
        v.value.clear();
        for(;;) {
            auto c = s.peek();
            if(std::istream::traits_type::eof() == c || ',' == c || '\n' == c)
                break;
            v.value.push_back(c);
            s.get();
        }
        return s;
    }

    friend std::ostream& operator<<(std::ostream& s, CsvString const& v) {
        return s << v.value;
    }
};

int main() {
    std::stringstream input("abc,123,true,3.14\n"
                            "def,456,false,2.718\n");

    typedef boost::tuple<CsvString, int, bool, double> CsvRow;

    using fusion::operator<<;
    std::cout << std::boolalpha;

    using fusion::operator>>;
    input >> std::boolalpha;
    input >> fusion::tuple_open("") >> fusion::tuple_close("\n") >> fusion::tuple_delimiter(',');

    for(CsvRow row; input >> row;)
        std::cout << row << '\n';
}

출력 :

(abc 123 true 3.14)
(def 456 false 2.718)

1

CSV 파일을 구문 분석하는 좋은 방법을 작성했으며 답으로 추가해야한다고 생각했습니다.

#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>

struct CSVDict
{
  std::vector< std::string > inputImages;
  std::vector< double > inputLabels;
};

/**
\brief Splits the string

\param str String to split
\param delim Delimiter on the basis of which splitting is to be done
\return results Output in the form of vector of strings
*/
std::vector<std::string> stringSplit( const std::string &str, const std::string &delim )
{
  std::vector<std::string> results;

  for (size_t i = 0; i < str.length(); i++)
  {
    std::string tempString = "";
    while ((str[i] != *delim.c_str()) && (i < str.length()))
    {
      tempString += str[i];
      i++;
    }
    results.push_back(tempString);
  }

  return results;
}

/**
\brief Parse the supplied CSV File and obtain Row and Column information. 

Assumptions:
1. Header information is in first row
2. Delimiters are only used to differentiate cell members

\param csvFileName The full path of the file to parse
\param inputColumns The string of input columns which contain the data to be used for further processing
\param inputLabels The string of input labels based on which further processing is to be done
\param delim The delimiters used in inputColumns and inputLabels
\return Vector of Vector of strings: Collection of rows and columns
*/
std::vector< CSVDict > parseCSVFile( const std::string &csvFileName, const std::string &inputColumns, const std::string &inputLabels, const std::string &delim )
{
  std::vector< CSVDict > return_CSVDict;
  std::vector< std::string > inputColumnsVec = stringSplit(inputColumns, delim), inputLabelsVec = stringSplit(inputLabels, delim);
  std::vector< std::vector< std::string > > returnVector;
  std::ifstream inFile(csvFileName.c_str());
  int row = 0;
  std::vector< size_t > inputColumnIndeces, inputLabelIndeces;
  for (std::string line; std::getline(inFile, line, '\n');)
  {
    CSVDict tempDict;
    std::vector< std::string > rowVec;
    line.erase(std::remove(line.begin(), line.end(), '"'), line.end());
    rowVec = stringSplit(line, delim);

    // for the first row, record the indeces of the inputColumns and inputLabels
    if (row == 0)
    {
      for (size_t i = 0; i < rowVec.size(); i++)
      {
        for (size_t j = 0; j < inputColumnsVec.size(); j++)
        {
          if (rowVec[i] == inputColumnsVec[j])
          {
            inputColumnIndeces.push_back(i);
          }
        }
        for (size_t j = 0; j < inputLabelsVec.size(); j++)
        {
          if (rowVec[i] == inputLabelsVec[j])
          {
            inputLabelIndeces.push_back(i);
          }
        }
      }
    }
    else
    {
      for (size_t i = 0; i < inputColumnIndeces.size(); i++)
      {
        tempDict.inputImages.push_back(rowVec[inputColumnIndeces[i]]);
      }
      for (size_t i = 0; i < inputLabelIndeces.size(); i++)
      {
        double test = std::atof(rowVec[inputLabelIndeces[i]].c_str());
        tempDict.inputLabels.push_back(std::atof(rowVec[inputLabelIndeces[i]].c_str()));
      }
      return_CSVDict.push_back(tempDict);
    }
    row++;
  }

  return return_CSVDict;
}

1

사용할 수 있습니다 std::regex.

파일 크기와 사용 가능한 메모리에 따라 한 줄씩 또는 전체적으로 파일을 읽을 수 있습니다 std::string.

파일읽으려면 다음 을 사용할 수 있습니다.

std::ifstream t("file.txt");
std::string sin((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());

그런 다음 실제로 필요에 맞게 사용자 정의 할 수있는 것과 일치 할 수 있습니다.

std::regex word_regex(",\\s]+");
auto what = 
    std::sregex_iterator(sin.begin(), sin.end(), word_regex);
auto wend = std::sregex_iterator();

std::vector<std::string> v;
for (;what!=wend ; wend) {
    std::smatch match = *what;
    v.push_back(match.str());
}

1

내가 지금 부스트하는 데 익숙하지 않기 때문에 더 간단한 해결책을 제안 할 것입니다. .csv 파일에 ','로 구분 된 각 줄에 10 개의 숫자가있는 100 개의 줄이 있다고 가정합니다. 다음 코드를 사용하여이 데이터를 배열 형태로로드 할 수 있습니다.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;

int main()
{
    int A[100][10];
    ifstream ifs;
    ifs.open("name_of_file.csv");
    string s1;
    char c;
    for(int k=0; k<100; k++)
    {
        getline(ifs,s1);
        stringstream stream(s1);
        int j=0;
        while(1)
        {
            stream >>A[k][j];
            stream >> c;
            j++;
            if(!stream) {break;}
        }
    }


}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.