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可能であれば近づかないでください。
2009

50
申し訳ありませんが、それはひどいアドバイスです。スピリットは常に適切なソリューションであるとは限りませんが、私はそれを使用しました-そしてそれを使い続けています-多くのプロジェクトで成功しています。同様のツール(Antlr、Lex / yaccなど)と比較すると、大きな利点があります。さて、CSVを解析することは、おそらくやり過ぎです...
MattyT、2009

4
@MattyT IMHO spiritは、パーサーコンビネーターライブラリで使用するのがかなり難しいです。Haskells (atto)parsecライブラリで(非常に楽しい)経験があったので、それ(スピリット)も同様にうまく機能すると期待していましたが、600行のコンパイラエラーで戦ったため、あきらめました。
2014

回答:


296

カンマと改行のエスケープを気にしない場合、
およびカンマと改行を引用符で埋め込めない場合(エスケープできない場合...)
は、約3行のコード(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とは!冗談だけ。
マーティンヨーク

4
@DarthVader:その広さによってばかげているというオーバーレイの広い声明。なぜそれが悪いのか、そしてなぜこの悪いことがこのコンテキストに当てはまるのかを明確にしたい場合。
マーティンヨーク

12
@DarthVader:広い一般化をするのはばかげていると思います。上記のコードは正しく動作するため、実際に何か問題があることがわかります。しかし、上記について具体的なコメントがあれば、私はこの文脈で間違いなく検討します。しかし、C#の一連の一般化されたルールに従って、それを別の言語に適用することによって、どうやってその結論に到達できるかがわかります。
マーティンヨーク

5
また、別のライブラリがどこかで定義しているためistream::operator>>(Eigenなど)、上記のコードで奇妙なリンクの問題が発生した場合inlineは、演算子宣言の前にを追加して修正します。
sebastian_k 2013年

3
これは、私が今まで見たイテレータクラスを作成する方法の最も単純で最もクリーンな例です。
ジャンカルロスポルテリ2015

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 2010

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

コードは有限状態マシンとして作成され、一度に1文字を消費します。考えた方が簡単だと思います。

#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

さらに、これらすべての切り替えステートメントを取得しました
Nikolaos Giotis

古いコンパイラを使用しているため、一番上の答えはうまくいきませんでした。:この答えは、ベクトルの初期化は、この必要な場合があり、働いていたconst char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

C ++文字列ライブラリツールキット(StrTkは)あなたがいずれかからデータをロードすることを可能にするトークングリッドクラス持つテキストファイル、文字列またはcharバッファを行と列の形式で、構文解析/プロセス彼らには。

行区切り文字と列区切り文字を指定するか、デフォルトをそのまま使用できます。

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.")。あなた自身でそれをしなければならないでしょう。
ラピオン2017

1
を使用する場合strtk、改行文字を含む二重引用符で囲まれたフィールドも手動で処理する必要があります。
ラピオン2017

29

escaped_list_separatorでBoost Tokenizerを使用できます。

escaped_list_separatorは、csvのスーパーセットを解析します。Boost :: tokenizer

これはBoostトークナイザーヘッダーファイルのみを使用し、Boostライブラリへのリンクは必要ありません。

次に例を示します(詳細については、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 2011年

この手法は機能しますが、パフォーマンスが非常に低いことがわかりました。1行に10個のフィールドがある90000行のCSVファイルの解析には、私の2 GHz Xeonで約8秒かかります。Python標準ライブラリのcsvモジュールは、同じファイルを約0.3秒で解析します。
ロブ・スモールシャー

@Robそれは面白いです-Python csvは何が違うのですか?
tofutim

1
@RobSmallshireこれは単純なサンプルコードであり、高性能のものではありません。このコードは、1行ごとにすべてのフィールドのコピーを作成します。より高いパフォーマンスを得るには、さまざまなオプションを使用して、コピーを作成する代わりに、バッファ内のフィールドへの参照のみを返します。
stefanB 2012

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
@ガーディナーナンセンス。小さなパーサーのコンパイル時間のヒットはそれほど大きくありませんが、コードを独自のコンパイルユニットに詰め込んで1回コンパイルするので、それも重要ではありません。次に、それをリンクするだけでよく、それはそれが得るのと同じくらい効率的です。そして、他のコメントと同様に、CSVのプロセッサの数と同じ数のCSVの方言があります。これは確かにあまり有用な方言ではありませんが、引用符で囲まれた値を処理するために簡単に拡張できます。
Konrad Rudolph

11
@konrad: "#include <boost / spirit / include / qi.hpp>"をメインのみを含む空のファイルに単に含めるだけで、2.ghzで実行されているcorei7上のMSVC 2012で9.7秒かかります。それは不必要な膨らみです。受け入れられた回答は同じマシンで2秒未満でコンパイルされます。「適切な」Boost.Spiritの例がコンパイルにかかる時間を想像するのは嫌です。
Gerdiner 2013年

11
@Gerdiner cvs処理が非常に大きいので、spiritを単純なものに使用する際のオーバーヘッドに同意する必要があります。

18

CSVを正しく解析することに関心がある場合、これはそれを行います...一度に1文字ずつ機能するため、比較的ゆっくりです。

 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年、フォード、E350、「超豪華トラック」

wikiで指定されているCSV形式では、単一引用符は二重引用符で処理する必要があると記載されています(escaped_list_separatorはすべての引用文字を削除します)。

1997年、フォード、E350、「スーパー ""豪華 ""トラック」

CSV形式では、バックスラッシュ文字を削除するように指定されていません(escaped_list_separatorはすべてのエスケープ文字を削除します)。

ブーストのescaped_list_separatorのデフォルトの動作を修正するための可能な回避策:

  1. 最初に、すべてのバックスラッシュ文字(\)を2つのバックスラッシュ文字(\\)に置き換えて、削除されないようにします。
  2. 次に、すべての二重引用符( "")を単一のバックスラッシュ文字と引用符(\ ")に置き換えます。

この回避策には、二重引用符で表される空のデータフィールドが単一引用符トークンに変換されるという副作用があります。トークンを反復処理するときは、トークンが単一引用符であるかどうかを確認し、空の文字列のように扱う必要があります。

きれいではありませんが、引用符内に改行がない限り機能します。


8

私のFOSSプロジェクトCSVfix更新されたリンクC ++で記述されたCSVストリームエディター)。CSVパーサーは賞品ではありませんが、機能します。コード全体を記述しなくても、パッケージ全体で必要なことを実行できます。

参照alib / SRC / a_csv.cpp CSVパーサのため、およびcsvlib / SRC / csved_ioman.cppIOManager::ReadCSV)使用例のために。


すばらしいようです...ステータスのベータ版/製品版はどうですか?
ニューロ

バージョン番号が示すように、ステータスは「開発中」です。バージョン1.0に進む前に、ユーザーからのフィードバックをもっと必要としています。さらに、CSVからXMLを生成するために、追加したい機能がいくつかあります。

ブックマークして、次回はすばらしい標準CSVファイルを処理しなければならないときに試してみる...
ニューロ

8

すべてのCSVの質問がここにリダイレクトされるように見えるので、私は自分の回答をここに投稿すると思いました。この回答は、質問者の質問に直接対処するものではありません。CSV形式であることがわかっているストリームで読み取ることができるようにしたかったのですが、各フィールドのタイプもすでにわかっていました。もちろん、以下のメソッドを使用して、すべてのフィールドを文字列型として処理できます。

CSV入力ストリームを使用できるようにする方法の例として、次の入力を検討してください(CSVに関するウィキペディアのページから取得)。

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

解析は、範囲ベースのforループです。

#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
いい仕事ですが、さらに3つ追加する必要があります。(1)ヘッダーを読み取る(2)名前でインデックスを付けるフィールドを提供する(3)同じ文字列のベクトルを再利用してループ内でメモリを再割り当てしない
Maksym Ganenko

@MaksymGanenko#3します。#2について詳しく教えてください。
m0meni 2017

1
行内の位置ではなく、ヘッダー(CSVテーブルの最初の行)で指定された名前でフィールドを取得すると非常に便利です。たとえば、「日付」フィールドを含むCSVテーブルが必要ですが、「日付」フィールドインデックスが何であるかがわかりません。
Maksym Ganenko 2017

1
@MaksymGanenkoああ、私はあなたが何を意味するのかわかります。コンパイル時にCSVの列がわかっている場合のためのgithub.com/ben-strasser/fast-cpp-csv-parserがあり、おそらく私のものよりも優れています。私が欲しかったのは、多くの異なるCSVに同じコードを使用したいが、事前にそれらがどのように見えるかわからない場合のためのCSVパーサーでした。そのため、おそらく#2は追加しませんが、将来的には#1を追加する予定です。
m0meni 2017

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は特定のタイプのです。コードは1行をスキャンし、各区切り文字までスキャンしてから、値を変換して(テンプレートコードのビットを使用して)タプルに直接ダンプします。

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には、独自の規則もあります。「インライン」キーワードは単なるインセンティブでなければなりません。
2016年

4

これは、Unicode CSVパーサーの別の実装です(wchar_tで動作します)。ジョナサン・レフラーが残りの部分を書いている間、私はそれの一部を書きました。

注:このパーサーは、特に壊れたまたは不正な形式をインポートする場合に、Excelの動作を可能な限り忠実に再現することを目的としています CSVファイルをます。

これは元の質問です- 複数行フィールドとエスケープされた二重引用符を含む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 2017

1
フィードバックをありがとう@MaksymGanenko最終行の「最後のデータ行」のバグを修正しました。言及された他の問題について-「ラベルが空のヘッダー」-それが何を指しているのかわかりませんか?ライブラリは空のラベル(引用符付きと引用符なしの両方)を処理する必要があります。ヘッダーの行/列なしでCSVを読み取ることもできますが、ユーザーがこれを指定する必要があります(col title id -1およびrow title id -1)。サポートを希望する特定のユースケースがある場合は、GitHubページで詳細を提供するか、バグを報告してください。ありがとう!
d99kris 2017

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 2014年

@Timmmmは、Stringクラスのsubstrメソッドを検索します。複数の文字を使用していることがわかります。\ nは改行文字であるため、この例では1文字としてカウントされます。全体として値全体を検索するわけではありません。個々の文字を検索しています。つまり、コンマまたは改行です。substrは、最初に検出された文字の位置を返し、どちらも検出されなかった場合は-1を返します。つまり、行の読み取りが終了したことになります。fpはファイル内の位置を内部的に追跡しているため、readCSVを呼び出すたびに1行ずつ移動します。
Martyn Shutt 2015年

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,",

ファイルを1文字ずつ読み取り、(文字列の)ベクトルに一度に1行を読み取るため、非常に大きなファイルに適しています。

使い方は

空の行が返されるまで繰り返します(ファイルの終わり)。行は、各エントリが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 ++継承Qt問題qstring


これは引用符でコンマを処理しません
Ezee

1

プロジェクトにboostを含めたくない場合(CSV解析のみを使用する場合はかなり大きなサイズになります...)

私はここでのCSV解析で運がありました:

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

引用符で囲まれたフィールドを処理しますが、インラインの\ n文字は処理しません(ほとんどの場合これで十分です)。


1
コンパイラーは不要なものをすべて取り除いてはいけませんか?
tofutim

1

これは古いスレッドですが、まだ検索結果の一番上にあるので、std :: stringstreamと、ここで見つけたYves Baumesによる単純な文字列置換メソッドを使用して自分のソリューションを追加しています。

次の例では、ファイルを1行ずつ読み取り、//で始まるコメント行を無視し、他の行を解析して文字列、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です。

ファイルのサイズと使用可能なメモリに応じて、1行ずつ、または完全にで読み取ることができます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ファイルに100行あり、各行に「、」で区切られた10個の数字があるとします。次のコードを使用して、このデータを配列の形式でロードできます。

#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.