CSV dosyalarını C ++ ile nasıl okuyabilir ve ayrıştırabilirim?


264

Yüklemek ve C ++ CSV dosya verilerini kullanmanız gerekir. Bu noktada gerçekten sadece virgülle ayrılmış bir ayrıştırıcı olabilir (yani yeni satırlardan ve virgüllerden kaçmak için endişelenmeyin). Temel ihtiyaç, yöntem her çağrıldığında bir sonraki satır için bir vektör döndürecek bir satır satır ayrıştırıcıdır.

Oldukça umut verici görünen bu makaleyi buldum: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Boost'un Ruhu'nu hiç kullanmadım, ama denemeye hazırım. Ama sadece daha basit bir çözüm yoksa gözden kaçırıyorum.


11
boost::spiritAyrıştırma için baktım . Dilbilgilerini ayrıştırmak için daha çok basit bir dosya biçimini ayrıştırdığınız için teşekkür ederiz. Ekibimden biri XML'i ayrıştırmak için kullanmaya çalışıyordu ve hata ayıklamak acı vericiydi. boost::spiritMümkünse uzak durun .
chrish

50
Üzgünüm chrish, ama bu korkunç bir tavsiye. Ruh her zaman uygun bir çözüm değildir, ancak bunu birkaç projede başarıyla kullandım ve kullanmaya devam ediyorum. Benzer araçlarla (Antlr, Lex / yacc vb.) Karşılaştırıldığında önemli avantajları vardır. Şimdi, CSV ayrıştırmak için muhtemelen aşırı ...
MattyT

4
@MattyT IMHO spirit, ayrıştırıcı birleştirici kitaplığı için oldukça zor. Haskells (atto)parseckütüphaneleri ile (çok hoş) bir deneyim yaşadım (ruhu) benzer şekilde çalışmasını bekledim , ancak 600 satır derleyici hatalarıyla savaştıktan sonra vazgeçtim.
fho

Yanıtlar:


296

Eğer virgül ve satırsonu kaçmayı umursamıyorsanız,
VE tırnak içine virgül ve satırsonu gömmek olamaz (o zaman kaçamaz ...)
o zaman sadece üç satır kod (OK 14 -> Ama onun tüm dosyayı okumak için sadece 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;
}

Sadece bir satırı temsil eden bir sınıf yaratırdım.
Ardından bu nesneye akış yapın:

#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";
    }
}

Ancak küçük bir çalışma ile teknik olarak bir yineleyici oluşturabiliriz:

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
ilk () sonraki (). Bu Java nedir! Sadece şaka.
Martin York

4
@DarthVader: Genişliğine göre aptalca bir yer paylaşımlı geniş açıklama. Bunun neden kötü olduğunu ve bu kötülüğün neden bu bağlamda geçerli olduğunu açıklamak istiyorsanız.
Martin York

12
@DarthVader: Bence geniş genellemeler yapmak aptalca. Yukarıdaki kod doğru çalışıyor, bu yüzden aslında onunla yanlış bir şey görebilirsiniz. Ama yukarıdakilerle ilgili özel bir yorumunuz varsa, kesinlikle bu bağlamda ele alacağım. Ama dikkatsizce C # için bir dizi genel kural izleyerek ve başka bir dile uygulayarak bu sonuca nasıl gelebileceğinizi görebilirsiniz.
Martin York

5
Ayrıca, yukarıdaki kodla garip bağlantı sorunları yaşarsanız, başka bir kütüphane istream::operator>>(Eigen gibi) tanımladığı inlineiçin, düzeltmek için operatör bildiriminden önce bir ekleyin .
sebastian_k

3
Bu, şimdiye kadar gördüğüm bir yineleyici sınıfının nasıl oluşturulacağının en basit ve en temiz örneğidir.
Giancarlo Sportelli

46

Boost Tokenizer kullanarak çözüm:

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
Boost tokenizer tüm CSV standardını tam olarak desteklemez, ancak bazı hızlı çözümler vardır. Bkz. Stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Makinenizde tüm destek kitaplığının olması mı gerekiyor, yoksa bunu yapmak için kodlarının bir alt kümesini kullanabilir misiniz? 256mb CSV ayrıştırma için çok benziyor ..
NPike

6
@NPike: Yalnızca gerçekten ihtiyacınız olan başlıkları çıkarmak için boost ile birlikte gelen bcp yardımcı programını kullanabilirsiniz .
ildjarn

46

Benim sürümüm standart C ++ 11 kütüphanesinden başka bir şey kullanmıyor. Excel CSV teklifiyle iyi başa çıkıyor:

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

Kod sonlu durum makinesi olarak yazılmıştır ve her seferinde bir karakter tüketmektedir. Bence akıl yürütmek daha kolay.

#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
teşekkürler, bu en eksiksiz cevap, burada gömülü çok kötü.
mihai

Bu iç içe dizeler vektörü, modern işlemciler için bir hareketsizdir. Önbellek yeteneklerini
atar

artı tüm bu geçiş ifadelerini
aldınız

Eski bir derleyicide olduğum için en iyi cevap benim için işe yaramadı. Bu cevap çalıştı, vektör başlatma gerektirebilir:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

C ++ dize Toolkit Kütüphanesi (StrTk) aralarından birini verilerini yüklemek olanak tanıyan bir jeton ızgara sınıfı vardır metin dosyaları, dizeleri veya karakter tamponları bir satır-sütun moda ve ayrıştırma / süreç onları için.

Satır sınırlayıcılarını ve sütun sınırlayıcılarını belirtebilir veya yalnızca varsayılanları kullanabilirsiniz.

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;
}

Daha fazla örnek Burada bulunabilir


1
Her ne kadar strtk çift tırnaklı alanları desteklese ve hatta çevredeki tırnak işaretlerini (üzerinden options.trim_dquotes = true) soysa da, iki katına çıkarılan çift tırnakların kaldırılmasını desteklemez (örn "She said ""oh no"", and left.". C-string olarak alan "She said \"oh no\", and left."). Bunu kendiniz yapmanız gerekecek.
rampion

1
Kullanırken strtk, yeni satır karakterleri içeren çift tırnaklı alanları da elle işlemeniz gerekir.
rampion

29

Boost Tokenizer'ı escaped_list_separator ile kullanabilirsiniz.

escaped_list_separator csv'nin bir üst kümesini ayrıştırır. Boost :: tokenizer

Bu yalnızca Takviye belirteç başlık dosyalarını kullanır, kitaplıkları artırmak için bağlantı gerekmez.

İşte bir örnek, ( ayrıntılar için C ++ 'da Boost Tokenizer ile CSV Dosyasını Ayrıştırma bölümüne bakın 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;
    }
}

Ve gömülü yeni satırları ayrıştırmak istiyorsanız mybyteofcode.blogspot.com/2010/11/… .
stefanB

Bu teknik çalışırken, performansının çok düşük olduğunu gördüm. 2 GHz Xeon'umda satır başına on alan içeren 90000 satırlı bir CSV dosyasını ayrıştırmak yaklaşık 8 saniye sürer. Python Standard Library csv modülü aynı dosyayı yaklaşık 0,3 saniyede ayrıştırır.
Rob Smallshire

@ Bu ilginç - Python csv farklı ne yapıyor?
tofutim

1
@RobSmallshire, yüksek performanslı bir kod değil, basit bir örnek koddur. Bu kod, satır başına tüm alanların kopyalarını oluşturur. Daha yüksek performans için farklı seçenekler kullanır ve yalnızca kopya yapmak yerine arabellekteki alanlara başvurular döndürürsünüz.
stefanB

29

CSV'leri ayrıştırmak için Spirit'i kullanmak aşırıya kaçmaz. Ruh, mikro ayrıştırma görevleri için çok uygundur. Örneğin, Spirit 2.1 ile, bu kadar kolay:

bool r = phrase_parse(first, last,

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

    space, v);

V vektörü değerlerle doldurulur. Bir dizi öğretici varBoost 1.41 ile piyasaya sürülen yeni Spirit 2.1 belgelerinde buna değinen var.

Öğretici basitten karmaşığa ilerler. CSV ayrıştırıcıları ortada bir yerde sunulur ve Spirit kullanımında çeşitli tekniklere değinir. Üretilen kod, elle yazılmış kod kadar sıkıdır. Oluşturulan montajcıyı kontrol edin!


18
Aslında, aşırı derecede dolu, vurulma derleme süresi muazzam ve basit "mikro-ayrıştırma görevleri için Spirit'i mantıksız hale getiriyor.
Gerdiner

13
Ayrıca yukarıdaki kodun CSV'yi ayrıştırmadığını, yalnızca virgülle ayrılmış vektör türünün bir aralığını ayrıştırdığını belirtmek isterim. Tırnakları, sütun türlerini vb. Ele almaz. Kısacası 19 soruya cevap veren bir şey için bana oy biraz şüpheli görünüyor.
Gerdiner

9
@Gerdiner Saçmalık. Küçük ayrıştırıcılar için harcanan derleme süresi o kadar büyük değil, ama aynı zamanda da önemli değil çünkü kodu kendi derleme birimine doldurup bir kez derlersiniz . O zaman sadece bağlantı kurmanız gerekir ve bu mümkün olduğunca etkilidir. Ve diğer yorumunuza gelince, CSV'nin işlemcileri olduğu kadar çok lehçesi var. Bu kesinlikle çok yararlı bir lehçe değildir, ancak alıntılanan değerleri işlemek için önemsiz bir şekilde genişletilebilir.
Konrad Rudolph

11
@konrad: Sadece bir ana dosya içeren boş bir dosyaya "#include <boost / spirit / include / qi.hpp>" dahil etmek ve başka hiçbir şey MSVC 2012 ile 2.ghz'de çalışan bir corei7'de 9.7 saniye sürmez. Gereksiz şişkinlik. Kabul edilen cevap aynı makinede 2 saniyenin altında derlenir, 'uygun' Boost.Spirit örneğinin ne kadar sürede derleneceğini hayal etmekten nefret ederim.
Gerdiner

11
@Gerdiner Sizinle cvs işlemenin çok büyük olduğu kadar basit bir şey için ruhu kullanma yükünü kabul etmek zorundayım.

18

Eğer varsa DO doğru CSV ayrıştırılırken umurumda, bu yapacağım ... nispeten yavaş bir anda tek bir karakter çalışır gibi.

 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, gömülü alıntı işaretlerini doğru işlemez (örn. "Bu dize" "gömülü alıntı işaretlerine sahiptir" "", "foo", 1))
Jeremy Friesner

14

CSV dosyaları için Boost Tokenizer escaped_list_separator kullanırken, aşağıdakilerden haberdar olunmalıdır:

  1. Bir çıkış karakteri gerektirir (varsayılan eğik çizgi - \)
  2. Bir ayırıcı / ayırıcı karakter gerektirir (varsayılan virgül -,)
  3. Bir tırnak karakteri gerektirir (varsayılan tırnak - ")

Wiki tarafından belirtilen CSV biçimi, veri alanlarının tırnak işaretleri içinde ayırıcılar içerebileceğini belirtir (desteklenir):

1997, Ford, E350, "Süper, lüks kamyon"

Wiki tarafından belirtilen CSV biçimi, tek tırnakların çift tırnaklarla ele alınması gerektiğini belirtir (escaped_list_separator tüm alıntı karakterlerini kaldırır):

1997, Ford, E350, "Süper" "lüks" "kamyon"

CSV biçimi, herhangi bir ters eğik çizgi karakterinin çıkarılması gerektiğini belirtmez (escaped_list_separator tüm çıkış karakterlerini kaldırır).

Destek escaped_list_separator varsayılan davranışını düzeltmek için olası bir geçici çözüm:

  1. Önce tüm ters eğik çizgi karakterlerini (\) iki ters eğik çizgi karakteri (\\) ile değiştirin, böylece sıyrılamazlar.
  2. İkinci olarak, tüm çift tırnak işaretlerini ("") tek bir eğik çizgi karakteri ve bir tırnak işareti (\ ") ile değiştirin

Bu geçici çözüm, çift tırnak ile temsil edilen boş veri alanlarının tek tırnak işareti içine dönüştürülmesinin yan etkisine sahiptir. Belirteçleri yinelediğinde, belirtecin tek tırnak olup olmadığını kontrol etmeli ve boş bir dize gibi davranmalıdır.

Güzel değil ama tırnak içinde yeni satırlar olmadığı sürece işe yarıyor.


8

FOSS projesi CSVfix'ime bakmak isteyebilirsiniz ( güncellenmiş bağlantıC ++ ile yazılmış bir CSV akış düzenleyicisi olan ) . CSV ayrıştırıcısı bir ödül değildir, ancak iş ve tüm paket herhangi bir kod yazmadan ihtiyacınız olanı yapabilir.

Bkz Alib / src / a_csv.cpp CSV çözümleyici için ve csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) bir kullanım, örneğin.


Harika görünüyor ... Peki ya beta / üretim?
nöro

Sürüm numaralarının önerdiği gibi durum "geliştiriliyor" dur. 1.0 sürümüne geçmeden önce kullanıcılardan daha fazla geri bildirim almam gerekiyor. Ayrıca, CSV'den XML üretimi yapmak için eklemek istediğim birkaç özellik daha var.

Yer işareti koyup, bir dahaki sefere bu harika standart CSV dosyalarıyla uğraşmak zorunda kalacağım ...
neuro

8

Tüm CSV soruları buraya yönlendiriliyor gibi göründüğünden, cevabımı buraya göndereceğimi düşündüm. Bu cevap doğrudan sorucunun sorusunu ele almaz. CSV formatında olduğu bilinen bir akışta okumak istedim ve her alanın türleri zaten biliniyordu. Tabii ki, aşağıdaki yöntem her alanı bir dize türü olarak işlemek için kullanılabilir.

CSV giriş akışını nasıl kullanmak istediğime bir örnek olarak, aşağıdaki girişi ( CSV'deki wikipedia sayfasından alınmıştır) düşünün :

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

Sonra böyle verileri okumak istedim:

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

Sonuçta bulduğum çözüm buydu.

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'deki yeni ayrılmaz özellik şablonları tarafından basitleştirilebilen aşağıdaki yardımcılarla:

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) };
};

Çevrimiçi deneyin!


6

Yalnızca başlık, C ++ 11 CSV ayrıştırıcısı yazdım . İyi test edilmiş, hızlıdır, tüm CSV spesifikasyonlarını (tırnak içine alınmış alanlar, tırnak içinde sınırlayıcı / sonlandırıcı, alıntıdan kaçış vb.) Destekler ve spesifikasyona uymayan CSV'leri hesaba katacak şekilde yapılandırılabilir.

Yapılandırma akıcı bir arayüz üzerinden yapılır:

// 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

Ayrıştırma sadece döngüye dayalı bir aralıktı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
Güzel bir iş, ama üç şey daha eklemeniz gerekiyor: (1) başlık okumak (2) isme göre indeksleme alanları sağlamak (3) aynı dizeleri vektörü yeniden kullanarak döngüdeki belleği yeniden tahsis etmeyin
Maksym Ganenko

@MaksymGanenko # 3 yapıyorum. # 2 hakkında ayrıntı verebilir misiniz?
m0meni

1
Alanları bir satırdaki konuma göre değil, başlıkta (CSV tablosunun ilk satırında) verilen ada göre almak çok yararlıdır. Örneğin, "Tarih" alanı ile CSV tablosu bekliyoruz, ancak üst üste "Tarih" alan dizini nedir bilmiyorum.
Maksym Ganenko

1
@MaksymGanenko ah Ne demek istediğini anlıyorum. Derleme zamanında CSV'nizin sütunlarını bildiğiniz için github.com/ben-strasser/fast-cpp-csv-parser vardır ve muhtemelen benimkinden daha iyidir. İstediğim, birçok farklı CSV için aynı kodu kullanmak istediğiniz ve vaktinden önce nasıl göründüklerini bilmediğiniz durumlar için bir CSV ayrıştırıcısıydı. Bu yüzden muhtemelen # 2 eklemeyeceğim, ancak gelecekte # 1 ekleyeceğim.
m0meni

5

Başka bir CSV I / O kütüphanesi burada bulunabilir:

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
Güzel, ama derleme sırasında sütun sayısını seçmeye zorlar. Birçok uygulama için pek kullanışlı değil.
quant_dev

5

C ++ 11'de Loki Astari'nin cevabına benzer başka bir çözüm . Buradaki satırlar std::tupleverilen türde. Kod bir satırı tarar, sonra her bir sınırlayıcıya kadar tarar ve sonra değeri doğrudan tuple dönüştürür ve döker (bir miktar şablon kodu ile).

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

Advanges:

  • oldukça temiz ve kullanımı basit, sadece C ++ 11.
  • içine otomatik tür dönüşümü std::tuple<t1, ...>yoluyla operator>>.

Ne kayıp:

  • kaçmak ve alıntı yapmak
  • hatalı biçimlendirilmiş CSV durumunda hata işleme yok.

Ana kod:

#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'a küçük bir çalışma örneği koydum ; Bazı sayısal verileri ayrıştırmak için kullanıyorum ve amacına hizmet etti.


1
Çizgileri önemsemeyebilirsiniz, çünkü derleyicilerin çoğu kendi başına karar verir. En azından Visual C ++ ile eminim. Yöntem belirtiminizden bağımsız olarak yöntemi satır içine alabilir.
MrPisarik

1
Bu yüzden onları açıkça işaretledim. Gcc ve Clang, çoğunlukla kullandıklarım, kendi sözleşmeleri de var. "Satır içi" bir anahtar kelime yalnızca bir teşvik olmalıdır.
Spak

4

Unicode CSV ayrıştırıcısının başka bir uygulaması (wchar_t ile çalışır). Jonathan Leffler gerisini yazarken, bir kısmını yazdım.

Not: Bu ayrıştırıcı, özellikle bozuk veya hatalı biçimlendirilmiş CSV dosyalarını içe aktarırken Excel'in davranışını olabildiğince yakın bir şekilde çoğaltmayı amaçlamaktadır .

Bu asıl soru - CSV dosyasını çok satırlı alanlar ve çıkış karakterli çift tırnak işaretleri ile ayrıştırma

Bu bir SSCCE (Kısa, Bağımsız, Doğru Örnek) kodudur.

#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 dosyalarını ayrıştırmak için kullanımı kolay bir C ++ kütüphanesine ihtiyacım vardı, ancak herhangi bir kullanılabilir bulamadım, bu yüzden bir tane oluşturdum. Rapidcsv , seçili veri tipinde vektörler olarak ayrıştırılmış sütunlara (veya satırlara) doğrudan erişim sağlayan C ++ 11 yalnızca başlık kitaplığıdır. Örneğin:

#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
İyi iş, ancak başlıkta boş etiketler varsa kütüphane düzgün çalışmıyor. Bu Excel / LibreOffice NxN tablosu için tipiktir. Ayrıca, son veri satırını atlayabilir. Ne yazık ki, lib'niz sağlam değil.
Maksym Ganenko

1
@MaksymGanenko için teşekkürler Teşekkürler son satır w / o son satır sonu için "veri son satırı" hata düzeltildi. Bahsedilen diğer konuya gelince - "boş etiketli başlıklar" - Ne anlama geldiğinden emin değilim? Kütüphane boş etiketleri (alıntılanmış ve alıntılanmamış) işlemelidir. Ayrıca CSV'yi başlık satırı / sütunu olmadan okuyabilir, ancak kullanıcının bunu belirtmesini gerektirir (col title id -1 ve row title id -1). Desteklenmesini istediğiniz belirli bir kullanım örneğiniz varsa, lütfen biraz daha ayrıntı verin veya GitHub sayfasında bir hata bildirin. Teşekkürler!
d99kris

2

Affedersiniz, ancak tüm bunlar birkaç kod satırını gizlemek için çok sayıda ayrıntılı sözdizimi gibi görünüyor.

Neden olmasın:

/**

  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;
}

Ee, neden ipte olurdu ",\n"?
Timmmm

@Timmmm, String sınıfının substr yöntemini arar ve bunun birden çok karakter aldığını görürsünüz, \ n yeni satır karakteri olduğundan, bu örnekte tek bir karakter olarak sayılır. Bütün değeri bir bütün olarak aramaz. Her bir karakteri arar; virgül veya satırsonu. substr bulduğu ilk karakterin konumunu ve hiçbirini bulamazsa -1 değerini döndürür, yani satırı okumayı bitirir. fp, dosyadaki konumu dahili olarak izler, böylece readCSV'ye yapılan her çağrı, her seferinde bir satır taşır.
Martyn Shutt

2

İşte bir matrisi okumak için kod, ayrıca matlab'da bir csvwrite fonksiyonuna sahip olduğunuzu unutmayın

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 işlevlerini kullanarak .csv dosyasını açabilir ve okuyabilirsiniz, ancak önemli olan verileri ayrıştırmaktır.

Data1.csv dosyanızın aşağıdaki gibi olduğunu varsayalım:

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

verileri tokenize edebilir ve char dizisinde saklayabilir ve daha sonra uygun dönüşümler için atoi () etc işlevini kullanabilirsiniz

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 mantığı tersine çevirir, virgül içermeyen ve daha sonra son içermeyen herhangi bir dizeyle eşleşmek anlamına gelir, önceki dizeyi sonlandıran virgülle eşleşmek anlamına gelir.


2

Yapmanız gereken ilk şey dosyanın var olduğundan emin olmaktır. Bunu başarmak için yoldaki dosya akışını denemeniz ve açmanız yeterlidir. Dosya akışını açtıktan sonra, beklendiği gibi çalışıp çalışmadığını görmek için stream.fail () kullanın.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Ayrıca, sağlanan dosyanın doğru dosya türü olduğunu doğrulamalısınız. Bunu yapmak için, dosya uzantısını bulana kadar sağlanan dosya yolunu gözden geçirmeniz gerekir. Dosya uzantısına sahip olduğunuzda, bunun bir .csv dosyası olduğundan emin olun.

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;
}

Bu işlev, daha sonra bir hata iletisinde kullanılan dosya uzantısını döndürür.

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;
}

Bu işlev aslında yukarıda oluşturulan hata kontrollerini çağırır ve daha sonra dosya boyunca ayrıştırır.

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

Gibi güzel bir şey kullandığınızda gurur duymalısınız boost::spirit

Burada (Neredeyse) bu bağlantı CSV özellikleri CSV özellikleri ile uyumlu bir ayrıştırıcı girişimi ( alanlar içinde satır kesmeleri gerek yoktu. Ayrıca virgül çevresindeki boşluklar göz ardı).

Bu kodu derlemek için 10 saniye beklemenin şok edici deneyimini aştıktan sonra, arkanıza yaslanıp keyfini çıkarabilirsiniz.

// 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;
}

Derleme:

make csvparser

Test ( Wikipedia'dan çalınan örnek ):

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

Bu çözüm bu 4 vakayı algılar

tam sınıf

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

Dosyayı karakter karakter okur ve her seferinde bir dizeye 1 satır okur, bu nedenle çok büyük dosyalar için uygundur.

Kullanımı

Boş bir satır dönene kadar yineleyin (dosya sonu). Satır, her girişin bir CSV sütunu olduğu bir vektördür.

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

sınıf bildirisi

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;
};

hayata geçirme

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

Ayrıca Qtkütüphanenin yeteneklerine de göz atabilirsiniz .

Düzenli ifade desteği vardır ve QString sınıfı, split()QStringList'in döndürülmesi, orijinal dizenin sağlanan bir ayırıcıyla bölünmesiyle elde edilen dizelerin listesi gibi güzel yöntemlere sahiptir . Csv dosyası için yeterli olmalı ..

Belirli bir başlık adı ile bir sütun almak için aşağıdakileri kullanın: c ++ kalıtım Qt sorunu qstring


bu tırnak içinde virgül kullanmaz
Ezee

1

Projenize destek eklemekle uğraşmak istemiyorsanız (kullanacağınız tek şey CSV ayrıştırma ise oldukça büyüktür ...)

Burada CSV ayrıştırmada şansım oldu:

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

Alıntılanan alanları işler - ancak satır içi \ n karakterlerini (çoğu kullanım için iyi olabilir) işlemez.


1
Derleyici gerekli olmayan her şeyi çıkarmamalı mı?
tofutim

1

Bu eski bir iş parçacığı ama yine de arama sonuçlarının en üstünde, bu yüzden burada buldum Yves Baumes tarafından std :: stringstream ve basit bir dize değiştirme yöntemi kullanarak benim çözüm ekliyorum.

Aşağıdaki örnek, bir dosyayı satır satır okuyacak, // ile başlayan yorum satırlarını yok sayacak ve diğer satırları dizeler, girişler ve çiftler kombinasyonuna ayrıştıracaktır. Stringstream ayrıştırma yapar, ancak boşluklar tarafından sınırlandırılacak alanları bekler, bu yüzden önce virgül boşluklara açmak için stringreplace kullanın. Sekmeleri ok işler, ancak alıntılanan dizelerle ilgilenmez.

Durumunuza bağlı olarak, kötü veya eksik giriş yok sayılır; bu durum iyi olabilir veya olmayabilir.

#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

Değeri ne olursa olsun, benim uygulama. Wstring girişi ile ilgilenir, ancak kolayca dizgiye ayarlanabilir. Alanlardaki satırsonu işlemez (uygulamam da yapmaz, ancak desteğini eklemek çok zor değildir) ve RFC'ye göre "\ r \ n" satır sonu ile uyumlu değildir (std :: getline), ancak boşluk kırpmayı ve çift tırnakları doğru şekilde (umarız) ele alır.

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

İhtiyacınız olan tek şey bir veri dosyasını iki katına (tamsayı yok, metin yok) yüklemekse, kullanıma hazır bir işlev.

#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

Bir başka hızlı ve kolay yol 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';
}

Çıktılar:

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

1

CSV dosyalarını ayrıştırmanın güzel bir yolunu yazdım ve bir cevap olarak eklemem gerektiğini düşündüm:

#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

Kullanmak mümkündür std::regex.

Dosyanızın boyutuna ve kullanabileceğiniz belleğe bağlı olarak, satır satır veya tamamen bir std::string.

Dosyayı okumak için aşağıdakileri kullanabilirsiniz:

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

o zaman aslında ihtiyaçlarınıza göre özelleştirilebilir bu ile eşleştirebilirsiniz.

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

Şu anda artırmak için kullanılmadığım için daha basit bir çözüm önereceğim. Diyelim ki .csv dosyanızda her satırda bir ',' ile ayrılmış 10 rakam içeren 100 satır var. Bu verileri aşağıdaki kodla bir dizi biçiminde yükleyebilirsiniz:

#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;}
        }
    }


}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.