ฉันจะอ่านและแยกไฟล์ CSV ใน C ++ ได้อย่างไร


264

ฉันต้องการโหลดและใช้ข้อมูลไฟล์ CSV ใน C ++ ณ จุดนี้มันสามารถเป็นตัวแยกวิเคราะห์เครื่องหมายจุลภาค (เช่นไม่ต้องกังวลเกี่ยวกับการหลบหนีบรรทัดใหม่และเครื่องหมายจุลภาค) ความต้องการหลักคือตัวแยกวิเคราะห์บรรทัดต่อบรรทัดที่จะส่งคืนเวกเตอร์สำหรับบรรทัดถัดไปในแต่ละครั้งที่เรียกใช้เมธอด

ฉันพบบทความนี้ซึ่งดูมีแนวโน้มมาก: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

ฉันไม่เคยใช้วิญญาณของ Boost แต่ยินดีที่จะลอง แต่ถ้าไม่มีวิธีแก้ปัญหาที่ตรงไปตรงมามากกว่านี้


11
ฉันดูที่boost::spiritการแยกวิเคราะห์ มันเป็นมากกว่าสำหรับการแยกวิเคราะห์ไวยากรณ์ขอบคุณการแยกรูปแบบไฟล์ง่าย ๆ มีคนในทีมของฉันพยายามใช้เพื่อแยกวิเคราะห์ XML และเป็นปัญหาในการแก้ไขข้อบกพร่อง อยู่ห่างจากboost::spiritถ้าเป็นไปได้
chrish

50
ขออภัยคริส แต่นั่นเป็นคำแนะนำที่แย่มาก วิญญาณไม่ใช่วิธีแก้ปัญหาที่เหมาะสมเสมอไป แต่ฉันใช้แล้ว - และใช้ต่อไป - สำเร็จในหลายโครงการ เมื่อเทียบกับเครื่องมือที่คล้ายกัน (Antlr, Lex / yacc ฯลฯ ) มันมีข้อดีที่สำคัญ ตอนนี้สำหรับการแยก CSV ก็อาจ overkill ...
MattyT

4
@MattyT IMHO spiritนั้นค่อนข้างยากที่จะใช้สำหรับไลบรารีตัวแยกวิเคราะห์ หลังจากได้รับประสบการณ์ที่น่าพอใจมากกับ(atto)parsecห้องสมุดHaskells ฉันคาดหวังว่ามัน (วิญญาณ) จะทำงานได้ดีเหมือนกัน แต่ยอมแพ้หลังจากต่อสู้กับ 600 compiler error error
fho

คำตอบ:


296

หากคุณไม่สนใจเกี่ยวกับการใช้เครื่องหมายจุลภาคและการขึ้นบรรทัดใหม่
และคุณไม่สามารถฝังเครื่องหมายจุลภาคและการขึ้นบรรทัดใหม่ในเครื่องหมายคำพูด (หากคุณไม่สามารถหลบหนีได้ ... )
ดังนั้นก็จะมีโค้ดประมาณสามบรรทัดเท่านั้น (OK 14 -> เพียง 15 เพื่ออ่านไฟล์ทั้งหมด)

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

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

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

ฉันจะสร้างคลาสที่แสดงแถว
จากนั้นสตรีมไปยังวัตถุนั้น:

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

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

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

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

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

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

แต่ด้วยงานเล็กน้อยเราสามารถสร้างตัววนซ้ำทางเทคนิคได้

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

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

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

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


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

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

20
ก่อน () ถัดไป () Java นี้คืออะไร! ล้อเล่นเท่านั้น
35490 Martin Martin York

4
@DarthVader: คำแถลงแบบซ้อนทับที่ว่าโดยความกว้างของมันนั้นโง่ หากคุณต้องการชี้แจงว่าทำไมจึงไม่ดีและทำไมความชั่วนี้ถึงมีผลในบริบทนี้
Martin York

12
@DarthVader: ฉันคิดว่ามันโง่ที่จะทำให้ภาพรวมในวงกว้าง รหัสข้างต้นทำงานอย่างถูกต้องเพื่อให้ฉันสามารถเห็นอะไรผิดปกติกับมัน แต่ถ้าคุณมีความคิดเห็นเฉพาะด้านบนฉันจะพิจารณาในบริบทนี้อย่างแน่นอน แต่ฉันสามารถดูว่าคุณสามารถสรุปได้อย่างไรโดยไม่สนใจกฎชุดทั่วไปสำหรับ C # และใช้กับภาษาอื่น
Martin York

5
นอกจากนี้หากคุณพบปัญหาการเชื่อมโยงแปลก ๆ กับรหัสข้างต้นเพราะมีห้องสมุดอื่นที่กำหนดไว้ istream::operator>> (เช่น Eigen) ให้เพิ่มinlineการประกาศก่อนดำเนินการเพื่อแก้ไข
sebastian_k

3
นี่เป็นตัวอย่างที่ง่ายและสะอาดที่สุดในการสร้างคลาส iterator ที่ฉันเคยเห็น
Giancarlo Sportelli

46

โซลูชันที่ใช้ Boost Tokenizer:

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

9
เครื่องมือเพิ่ม tokenizer ไม่สนับสนุนมาตรฐาน CSV ทั้งหมด แต่ก็มีวิธีแก้ไขปัญหาบางอย่างอย่างรวดเร็ว ดูstackoverflow.com/questions/1120140/csv-parser-in-c/ …
Rolf Kristensen

3
คุณต้องมีทั้งห้องสมุดเพิ่มในเครื่องของคุณหรือคุณสามารถใช้ส่วนย่อยของรหัสของพวกเขาในการทำเช่นนี้? 256mb ดูเหมือนจะเป็นเรื่องที่มากสำหรับการแยกวิเคราะห์ CSV ..
NPike

6
@NPike: คุณสามารถใช้ยูทิลิตีbcpที่มาพร้อมกับบูสต์เพื่อแยกเฉพาะส่วนหัวที่คุณต้องการ
ildjarn

46

รุ่นของฉันไม่ได้ใช้อะไรนอกจากห้องสมุด C ++ 11 มาตรฐาน สามารถอ้างอิงได้ดีกับใบเสนอราคาของ Excel CSV:

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

รหัสถูกเขียนเป็นเครื่องสถานะ จำกัด และใช้อักขระทีละตัว ฉันคิดว่ามันง่ายกว่าที่จะให้เหตุผล

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

enum class CSVState {
    UnquotedField,
    QuotedField,
    QuotedQuote
};

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

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

6
ขอบคุณฉันคิดว่านี่เป็นคำตอบที่สมบูรณ์แบบที่สุดแย่มากที่ฝังไว้ที่นี่
mihai

เวกเตอร์ของสตริงที่ซ้อนกันนี้เป็นตัวเลือกสำหรับโปรเซสเซอร์สมัยใหม่ ละทิ้งความสามารถในการแคช
Nikolaos Giotis

รวมทั้งคุณได้รับงบเปลี่ยนเหล่านั้นทั้งหมด
Nikolaos Giotis

คำตอบยอดนิยมใช้ไม่ได้กับฉันเพราะฉันเป็นคอมไพเลอร์รุ่นเก่า คำตอบนี้ใช้ได้ผลการเริ่มต้นของเวกเตอร์อาจต้องใช้สิ่งนี้:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

c ++ String Toolkit ห้องสมุด (StrTk)มีระดับตารางโทเค็นที่ช่วยให้คุณโหลดข้อมูลทั้งจากไฟล์ข้อความสตริงหรือบัฟเฟอร์ถ่านและการแยก / กระบวนการพวกเขาในแฟชั่นแถวคอลัมน์

คุณสามารถระบุตัวคั่นแถวและตัวคั่นคอลัมน์หรือเพียงแค่ใช้ค่าเริ่มต้น

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

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

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

ตัวอย่างเพิ่มเติมสามารถพบได้ที่นี่


1
แม้ว่าstrtk รองรับเขตข้อมูล doublequotedและแม้กระทั่งการแยกเครื่องหมายอัญประกาศล้อมรอบ (ผ่านoptions.trim_dquotes = true) แต่ก็ไม่สนับสนุนการลบ doublequotes สองเท่า (เช่นเขตข้อมูล"She said ""oh no"", and left."เป็น c-string "She said \"oh no\", and left.") คุณจะต้องทำเอง
rampion

1
เมื่อใช้strtkงานคุณจะต้องจัดการฟิลด์ที่มีเครื่องหมายคำพูดคู่ที่มีอักขระขึ้นบรรทัดใหม่ด้วยตนเอง
rampion

29

คุณสามารถใช้ Boost Tokenizer กับ escaped_list_separator

escaped_list_separatorแยกวิเคราะห์ superset ของ csv Boost :: tokenizer

ใช้เฉพาะไฟล์ส่วนหัวของ Boost tokenizer เท่านั้นไม่มีการเชื่อมโยงเพื่อเพิ่มไลบรารีที่จำเป็น

นี่คือตัวอย่าง (ดูไฟล์ CSV แยกวิเคราะห์ด้วย Boost Tokenizer ใน C ++สำหรับรายละเอียดหรือ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

ในขณะที่เทคนิคนี้ใช้งานได้ฉันพบว่ามันมีประสิทธิภาพต่ำมาก การแยกไฟล์ CSV 90000 บรรทัดที่มีสิบฟิลด์ต่อบรรทัดใช้เวลาประมาณ 8 วินาทีใน Xeon 2 GHz ของฉัน โมดูล csv Python Standard Library แยกวิเคราะห์ไฟล์เดียวกันในเวลาประมาณ 0.3 วินาที
Rob Smallshire

@Rob นั้นน่าสนใจ - Python csv แตกต่างกันอย่างไร?
tofutim

1
@RobSmallshire มันเป็นรหัสตัวอย่างง่ายๆที่ไม่ได้มีประสิทธิภาพสูง รหัสนี้ทำสำเนาของฟิลด์ทั้งหมดต่อบรรทัด เพื่อประสิทธิภาพที่สูงขึ้นคุณจะต้องใช้ตัวเลือกที่แตกต่างกันและคืนค่าการอ้างอิงไปยังเขตข้อมูลในบัฟเฟอร์แทนการทำสำเนา
stefanB

29

ไม่มีการใช้ overkill ในการแยก Spirit สำหรับ CSV วิญญาณเหมาะสำหรับงานแยกวิเคราะห์ขนาดเล็ก ตัวอย่างเช่นด้วย Spirit 2.1 มันง่ายเหมือน:

bool r = phrase_parse(first, last,

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

    space, v);

เวกเตอร์, v, ได้รับการอัดแน่นไปด้วยค่า มีชุดของบทเรียนสัมผัสกับสิ่งนี้ในเอกสาร Spirit 2.1 ใหม่ที่เพิ่งเปิดตัวพร้อม Boost 1.41

บทช่วยสอนดำเนินการจากเรียบง่ายไปจนถึงซับซ้อน ตัวแยกวิเคราะห์ CSV นำเสนอบางแห่งที่อยู่ตรงกลางและสัมผัสกับเทคนิคต่าง ๆ ในการใช้วิญญาณ รหัสที่สร้างขึ้นนั้นแน่นพอ ๆ กับโค้ดที่เขียนด้วยมือ ตรวจสอบแอสเซมเบลอร์ที่สร้างขึ้น!


18
ที่จริงแล้วมันเกินความจริงเวลาในการรวบรวมเป็นอย่างมากและทำให้การใช้ Spirit สำหรับ "งานการแยกวิเคราะห์อย่างง่าย" ไม่มีเหตุผล
Gerdiner

13
นอกจากนี้ฉันต้องการชี้ให้เห็นว่ารหัสข้างต้นไม่แยกวิเคราะห์ CSV มันแยกวิเคราะห์ช่วงของประเภทของเวกเตอร์ที่คั่นด้วยเครื่องหมายจุลภาค มันไม่ได้จัดการกับคำพูด, คอลัมน์ประเภทต่าง ๆ ฯลฯ ในสั้น ๆ 19 คะแนนสำหรับบางสิ่งที่ตอบคำถามดูเหมือนจะเป็นเรื่องน่าสงสัยสำหรับฉัน
Gerdiner

9
@ Gerdiner เรื่องไร้สาระ ตีรวบรวมเวลาสำหรับ parsers ขนาดเล็กไม่ว่าใหญ่ แต่ก็ยังไม่เกี่ยวข้องเพราะคุณสิ่งที่รหัสลงในหน่วยการสะสมของตัวเองและรวบรวมมันครั้งเดียว จากนั้นคุณจะต้องเชื่อมโยงและมีประสิทธิภาพเท่าที่จะได้รับ และสำหรับความคิดเห็นอื่น ๆ ของคุณนั้นมีภาษาท้องถิ่นของ CSV มากเช่นเดียวกับที่มีโปรเซสเซอร์สำหรับมัน อันนี้แน่นอนไม่ได้เป็นภาษาที่มีประโยชน์มาก แต่มันสามารถขยายเล็กน้อยเพื่อจัดการค่าที่ยกมา
Konrad Rudolph

11
@konrad: เพียงแค่รวม "#include <boost / spirit / include / qi.hpp>" ในไฟล์เปล่าที่มีเพียงไฟล์หลักและไม่มีสิ่งใดที่ใช้ 9.7sec กับ MSVC 2012 บน corei7 ที่ทำงานที่ 2.ghz มันไม่จำเป็นต้องขยายออก คำตอบที่ได้รับการยอมรับรวบรวมใน 2secs ในเครื่องเดียวกันฉันไม่อยากจินตนาการว่า Boost'Spirit 'ตัวอย่างที่ถูกต้องจะใช้เวลาในการคอมไพล์นานแค่ไหน
Gerdiner

11
@ Gerdiner ฉันต้องเห็นด้วยกับคุณค่าใช้จ่ายในการใช้จิตวิญญาณสำหรับบางสิ่งบางอย่างง่าย ๆ เช่นการประมวลผล cvs ดีเกินไป

18

ถ้าคุณทำดูแลเกี่ยวกับการแยก CSV อย่างถูกต้องนี้จะทำมัน ... ค่อนข้างช้าในขณะที่มันทำงานอย่างใดอย่างหนึ่งถ่านในเวลา

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

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

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

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

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

          aChar++;
       }

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

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

AFAICT นี้จะไม่จัดการเครื่องหมายคำพูดฝังตัวอย่างถูกต้อง (เช่น "สตริงนี้มี" "เครื่องหมายคำพูดฝัง" "", "foo", 1))
Jeremy Friesner

14

เมื่อใช้ Boost Tokenizer escaped_list_separator สำหรับไฟล์ CSV คุณควรทราบสิ่งต่อไปนี้:

  1. มันต้องมีอักขระเลี่ยง (ค่าเริ่มต้นแบ็กสแลช - \)
  2. มันต้องมีตัวแยก / seperator-character (จุลภาคเริ่มต้น -,)
  3. ต้องมีเครื่องหมายคำพูด (เครื่องหมายคำพูดเริ่มต้น - ")

รูปแบบ CSV ระบุโดย wiki ระบุว่าเขตข้อมูลสามารถมีตัวคั่นในเครื่องหมายคำพูด (สนับสนุน):

ปี 1997 ฟอร์ด E350 รถบรรทุกสุดหรู

รูปแบบ CSV ที่ระบุโดย wiki ระบุว่าควรใช้เครื่องหมายอัญประกาศคู่กับเครื่องหมายคำพูดคู่ (escaped_list_separator จะตัดอักขระคำพูดทั้งหมดออก):

ปี 1997 ฟอร์ด E350 "Super" "หรูหรา" "รถบรรทุก"

รูปแบบ CSV ไม่ได้ระบุว่าควรลบอักขระแบ็กสแลชออก (escaped_list_separator จะตัดอักขระ Escape ทั้งหมดออก)

วิธีแก้ไขที่เป็นไปได้เพื่อแก้ไขพฤติกรรมเริ่มต้นของ boost escaped_list_separator:

  1. ขั้นแรกให้แทนที่อักขระแบ็กสแลชทั้งหมด (\) ด้วยอักขระแบ็กสแลชสองตัว (\\) เพื่อให้ไม่ถูกถอดออก
  2. ประการที่สองแทนที่เครื่องหมายคำพูดคู่ ("") ทั้งหมดด้วยอักขระแบ็กสแลชเดี่ยวและเครื่องหมายคำพูด (\ ")

วิธีแก้ไขนี้มีผลข้างเคียงที่เขตข้อมูลว่างที่แสดงด้วยเครื่องหมายคำพูดคู่จะถูกเปลี่ยนเป็นเครื่องหมายคำพูดเดียว เมื่อวนซ้ำโทเค็นจะต้องตรวจสอบว่าโทเค็นเป็นเครื่องหมายคำพูดเดี่ยวหรือไม่และถือว่าเป็นสตริงว่างเปล่า

ไม่สวย แต่ใช้งานได้หากไม่มีการขึ้นบรรทัดใหม่ภายในเครื่องหมายคำพูด


8

คุณอาจต้องการดูCSVfixโครงการ FOSS ของฉัน( ลิงก์ที่อัปเดตแล้ว) ) ซึ่งเป็นตัวแก้ไขสตรีม CSV ที่เขียนด้วย C ++ ตัวแยกวิเคราะห์ CSV ไม่ใช่รางวัล แต่ทำหน้าที่และแพคเกจทั้งหมดอาจทำสิ่งที่คุณต้องการโดยไม่ต้องเขียนโค้ดใด ๆ

ดูalib / src / a_csv.cppสำหรับตัวแยกวิเคราะห์ CSV และcsvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) สำหรับตัวอย่างการใช้งาน


ดูเหมือนดี ... แล้วสถานะเบต้า / การผลิตล่ะ?
ประสาท

สถานะคือ "กำลังพัฒนา" ตามที่แนะนำโดยหมายเลขรุ่น ฉันต้องการฟีดย้อนกลับจากผู้ใช้มากขึ้นก่อนไปที่เวอร์ชัน 1.0 นอกจากนี้ฉันมีคุณสมบัติอีกสองสามอย่างที่ฉันต้องการเพิ่มเกี่ยวกับการผลิต XML จาก CSV

Bookmarking มันและจะให้มันลองครั้งต่อไปผมต้องจัดการกับผู้ที่ยอดเยี่ยมไฟล์ 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

ฉันเขียน parser CSV ส่วนหัวเท่านั้น C ++ 11parser รองรับการทดสอบอย่างรวดเร็วและรวดเร็วรองรับข้อมูลจำเพาะ CSV ทั้งหมด (ช่องที่ยกมา, ตัวคั่น / ตัวยุติในการเสนอราคา, การอ้างคำพูด ฯลฯ ) และสามารถกำหนดให้บัญชี CSVs ที่ไม่เป็นไปตามข้อกำหนด

การกำหนดค่าจะทำผ่านส่วนต่อประสานที่คล่องแคล่ว:

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

การแยกเป็นเพียงช่วงสำหรับลูป:

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

using namespace aria::csv;

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

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

1
ทำได้ดี แต่คุณต้องเพิ่มอีกสามสิ่ง: (1) อ่านส่วนหัว (2) จัดทำดัชนีฟิลด์โดยใช้ชื่อ (3) ไม่จัดสรรหน่วยความจำแบบวนซ้ำโดยใช้เวกเตอร์ของสตริงซ้ำ
Maksym Ganenko

@MaksymGanenko ฉันทำ # 3 คุณอธิบายรายละเอียดเกี่ยวกับ # 2 ได้ไหม?
m0meni

1
มันมีประโยชน์มากในการรับเขตข้อมูลไม่ใช่ตำแหน่งในแถว แต่ตามชื่อที่ให้ไว้ในส่วนหัว (ในแถวแรกของตาราง CSV) ตัวอย่างเช่นฉันคาดหวังว่าตาราง CSV พร้อมฟิลด์ "วันที่" แต่ฉันไม่ทราบว่าดัชนีฟิลด์ "วันที่" ในแถวคืออะไร
Maksym Ganenko

1
@MaksymGanenko อ่าฉันเห็นว่าคุณหมายถึงอะไร มีgithub.com/ben-strasser/fast-cpp-csv-parserสำหรับเมื่อคุณรู้ว่าคอลัมน์ของ CSV ของคุณในเวลารวบรวมและมันอาจจะดีกว่าของฉัน สิ่งที่ฉันต้องการคือตัวแยกวิเคราะห์ CSV สำหรับกรณีที่คุณต้องการใช้รหัสเดียวกันสำหรับ CSV ที่แตกต่างกันจำนวนมากและไม่ทราบว่าพวกเขามีลักษณะอย่างไรก่อนเวลา ดังนั้นฉันอาจจะไม่เพิ่ม # 2 แต่ฉันจะเพิ่ม # 1 ในอนาคต
m0meni

5

ห้องสมุด CSV I / O อื่นสามารถดูได้ที่นี่:

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

#include "csv.h"

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

2
ดี แต่มันบังคับให้คุณเลือกจำนวนคอลัมน์ในเวลารวบรวม ไม่ค่อยมีประโยชน์สำหรับแอปพลิเคชั่นมากมาย
quant_dev

5

โซลูชันอื่นที่คล้ายคลึงกับคำตอบของ Loki Astariใน C ++ 11 แถวที่นี่std::tupleเป็นประเภทที่กำหนด รหัสจะสแกนหนึ่งบรรทัดจากนั้นสแกนจนกระทั่งตัวคั่นแต่ละตัวจากนั้นแปลงและทิ้งค่าลงใน tuple โดยตรง (พร้อมรหัสเทมเพลตเล็กน้อย)

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

Advanges:

  • ค่อนข้างสะอาดและใช้งานง่ายเพียง C ++ 11
  • แปลงชนิดอัตโนมัติเข้าผ่านstd::tuple<t1, ...>operator>>

สิ่งที่ขาดหายไป:

  • หลบหนีและการอ้างอิง
  • ไม่มีการจัดการข้อผิดพลาดในกรณีที่ CSV ไม่ถูกต้อง

รหัสหลัก:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ฉันใส่เป็นตัวอย่างการทำงานเล็ก ๆ บนGitHub ; ฉันใช้มันเพื่อแยกวิเคราะห์ข้อมูลเชิงตัวเลขและมันทำหน้าที่ตามวัตถุประสงค์


1
คุณอาจไม่สนใจเกี่ยวกับการอินไลน์เนื่องจากคอมไพเลอร์ส่วนใหญ่ตัดสินใจด้วยตัวเอง อย่างน้อยฉันแน่ใจใน Visual C ++ มันสามารถวิธีการแบบอินไลน์เป็นอิสระจากข้อกำหนดวิธีการของคุณ
MrPisarik

1
นั่นคือเหตุผลที่ฉันทำเครื่องหมายพวกเขาอย่างชัดเจน Gcc และ Clang ที่ฉันใช้เป็นส่วนใหญ่มีแบบแผนของตัวเองเช่นกัน คำหลัก "อินไลน์" ควรเป็นเพียงสิ่งจูงใจ
Spak

4

นี่คือการใช้งานตัวแยกวิเคราะห์ Unicode CSV อีกอัน (ทำงานกับ wchar_t) ฉันเขียนบางส่วนในขณะที่ Jonathan Leffler เขียนส่วนที่เหลือ

หมายเหตุ: ตัวแยกวิเคราะห์นี้มีวัตถุประสงค์เพื่อจำลองพฤติกรรมของ 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

ฉันต้องการไลบรารี่ C ++ ที่ใช้งานง่ายสำหรับการแยกไฟล์ CSV แต่หาไม่ได้เลยฉันก็เลยสร้างมันขึ้นมา 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 นอกจากนี้อาจข้ามบรรทัดสุดท้ายของข้อมูล น่าเสียดายที่ lib ของคุณไม่แข็งแกร่ง
Maksym Ganenko

1
ขอบคุณสำหรับข้อเสนอแนะ @MaksymGanenko ฉันได้แก้ไขข้อผิดพลาด "บรรทัดสุดท้ายของข้อมูล" สำหรับบรรทัดสุดท้ายโดยไม่มีตัวแบ่งบรรทัดต่อท้าย สำหรับประเด็นอื่น ๆ ที่กล่าวถึง - "ส่วนหัวที่มีป้ายกำกับว่างเปล่า" - ฉันไม่แน่ใจว่ามันหมายถึงอะไร ไลบรารีควรจัดการป้ายกำกับที่ว่างเปล่า (ทั้งที่ยกมาและไม่อ้างอิง) นอกจากนี้ยังสามารถอ่าน CSV โดยไม่ต้องมีแถว / คอลัมน์ส่วนหัว แต่จากนั้นผู้ใช้ต้องระบุสิ่งนี้ (คอลัมน์ชื่อ id -1 และชื่อแถว id -1) โปรดระบุรายละเอียดเพิ่มเติมหรือรายงานข้อผิดพลาดที่หน้า GitHub หากคุณมีกรณีการใช้งานเฉพาะที่คุณต้องการดูการสนับสนุน ขอบคุณ!
d99kris

2

ขอโทษด้วยนะ แต่ทั้งหมดนี้ดูเหมือนว่าจะเป็นเรื่องของไวยากรณ์ที่ซับซ้อนเพื่อซ่อนโค้ดสองสามบรรทัด

ทำไมไม่ทำอย่างนี้:

/**

  Read line from a CSV file

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

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

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

    return 0;
}

เอ่อทำไมจะมี",\n"ในสตริง?
Timmmm

@Timmmm ค้นหาวิธี substr ของคลาส String และคุณจะเห็นว่าใช้อักขระหลายตัว \ n เป็นอักขระขึ้นบรรทัดใหม่ดังนั้นจึงนับเป็นอักขระตัวเดียวในอินสแตนซ์นี้ มันไม่ได้ค้นหาคุณค่าทั้งหมดโดยรวม มันกำลังค้นหาตัวละครแต่ละตัว คือจุลภาคหรือขึ้นบรรทัดใหม่ substr จะส่งคืนตำแหน่งของอักขระตัวแรกที่ค้นหาและ -1 หากพบว่าไม่ใช่ซึ่งหมายความว่าอ่านบรรทัดเสร็จแล้ว fp ติดตามตำแหน่งในไฟล์ภายในดังนั้นการเรียกไปยัง readCSV แต่ละครั้งจะย้ายทีละหนึ่งแถว
Martyn Shutt

2

นี่คือโค้ดสำหรับอ่านเมทริกซ์โปรดทราบว่าคุณมีฟังก์ชั่น csvwrite ใน MATLAB

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

คุณสามารถเปิดและอ่านไฟล์. csv โดยใช้ฟังก์ชั่น fopen, fscanf แต่สิ่งที่สำคัญคือการแยกวิเคราะห์ข้อมูลวิธีที่ง่ายที่สุดในการแยกวิเคราะห์ข้อมูลโดยใช้ตัวคั่นในกรณีของ. csv ตัวคั่นคือ ','

สมมติว่าไฟล์ data1.csv ของคุณเป็นดังนี้:

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

คุณสามารถโทเค็นข้อมูลและจัดเก็บในอาร์เรย์ถ่านและภายหลังใช้ฟังก์ชั่น 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 invers logic หมายถึงจับคู่สตริงใด ๆ ที่ไม่มีเครื่องหมายจุลภาคแล้วสุดท้ายบอกว่าจับคู่เครื่องหมายจุลภาคที่ยกเลิกสตริงก่อนหน้า


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

ที่นี่ฉันพยายาม parser (เกือบ) ปฏิบัติตามข้อกำหนด 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 แถวต่อครั้งเพื่อเวกเตอร์ (ของสตริง) จึงเหมาะสำหรับไฟล์ที่มีขนาดใหญ่มาก

การใช้งานคือ

ทำซ้ำจนกว่าจะส่งคืนแถวว่าง (สิ้นสุดไฟล์) แถวคือเวกเตอร์ที่แต่ละรายการเป็นคอลัมน์ CSV

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

ประกาศคลาส

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

การดำเนินการ

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

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

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

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

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

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

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

    default:
      column += c;
      break;
    }
  }

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

1

คุณสามารถดูความสามารถของQtห้องสมุด

มันมีการสนับสนุนการแสดงออกปกติและระดับ QString มีวิธีการที่ดีเช่นsplit()กลับ QStringList รายการของสตริงที่ได้รับจากการแยกสตริงเดิมที่มีตัวคั่นให้ ควรจะพอเพียงสำหรับไฟล์ csv ..

เพื่อรับคอลัมน์ที่มีชื่อส่วนหัวที่กำหนดฉันใช้ต่อไปนี้: c ++ inheritance Qt problem qstring


สิ่งนี้จะไม่จัดการเครื่องหมายจุลภาคในเครื่องหมายคำพูด
Ezee

1

หากคุณไม่ต้องการที่จะจัดการกับรวมถึงการเพิ่มในโครงการของคุณ (มันมีขนาดใหญ่มากถ้าสิ่งที่คุณจะใช้สำหรับมันคือการแยกวิเคราะห์ CSV ... )

ฉันโชคดีกับการแยกวิเคราะห์ CSV ที่นี่:

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

มันจัดการเขตข้อมูลที่ยกมา - แต่ไม่ได้จัดการกับตัวอักษรแบบอินไลน์ \ n (ซึ่งอาจจะดีสำหรับการใช้งานส่วนใหญ่)


1
คอมไพเลอร์ไม่ควรตัดทุกอย่างที่ไม่จำเป็นออกใช่ไหม
tofutim

1

นี่คือเธรดเก่า แต่ยังคงอยู่ที่ด้านบนของผลการค้นหาดังนั้นฉันจึงเพิ่มโซลูชันโดยใช้ std :: stringstream และวิธีการแทนที่สตริงอย่างง่ายโดย Yves Baumes ที่ฉันพบที่นี่

ตัวอย่างต่อไปนี้จะอ่านบรรทัดไฟล์ตามบรรทัดละเว้นบรรทัดข้อคิดเห็นที่ขึ้นต้นด้วย // และแยกบรรทัดอื่น ๆ เป็นการรวมกันของสตริง ints และ doubles 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 แต่สามารถปรับเป็นสตริงได้อย่างง่ายดาย มันไม่ได้จัดการกับการขึ้นบรรทัดใหม่ในฟิลด์ (เนื่องจากแอปพลิเคชันของฉันไม่ได้เป็นอย่างใดอย่างหนึ่ง แต่การเพิ่มการสนับสนุนไม่ยากเกินไป) และมันไม่สอดคล้องกับจุดสิ้นสุดของบรรทัด "\ r \ n" ตาม RFC (สมมติว่าคุณใช้ std :: getline) แต่มันจัดการกับการตัดช่องว่างและเครื่องหมายคำพูดคู่ได้อย่างถูกต้อง (หวังว่า)

using namespace std;

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

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

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

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

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

    }
    return r;
}

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

    fields.clear();

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

    return fields.size();
}

1

นี่คือฟังก์ชั่นที่พร้อมใช้งานหากคุณต้องการโหลดไฟล์ข้อมูลเป็นสองเท่า (ไม่มีจำนวนเต็มไม่มีข้อความ)

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

using namespace std;

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

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

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

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

1

อีกวิธีที่ง่ายและรวดเร็วคือการใช้Boost.Fusion I/O:

#include <iostream>
#include <sstream>

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

namespace fusion = boost::fusion;

struct CsvString
{
    std::string value;

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

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

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

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

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

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

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

ขาออก:

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

1

ฉันเขียนวิธีที่ดีในการแยกไฟล์ CSV และฉันคิดว่าฉันควรเพิ่มเป็นคำตอบ:

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

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

/**
\brief Splits the string

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

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

  return results;
}

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

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

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

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

  return return_CSVDict;
}

1

std::regexมันเป็นไปได้ที่จะใช้

std::stringขึ้นอยู่กับขนาดของไฟล์และหน่วยความจำที่มีให้คุณคุณก็เป็นไปได้อ่านมันเส้นทั้งเส้นหรือทั้งหมดใน

หากต้องการอ่านไฟล์สามารถใช้:

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

จากนั้นคุณสามารถจับคู่กับสิ่งนี้ซึ่งปรับแต่งได้ตามความต้องการของคุณ

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

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

1

เนื่องจากฉันไม่ได้ใช้เพื่อเพิ่มตอนนี้ฉันจะแนะนำวิธีแก้ปัญหาที่ง่ายขึ้น สมมติว่าไฟล์. csv ของคุณมี 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.