Як я можу читати та аналізувати файли CSV у C ++?


264

Мені потрібно завантажити та використовувати дані файлів CSV в C ++. На даний момент це може бути просто аналізатор, розділений комою (тобто не хвилюйтесь про те, щоб уникнути нових рядків і коми). Основна потреба - це рядковий рядковий аналізатор, який повертає вектор для наступного рядка щоразу, коли метод викликається.

Я знайшов цю статтю досить перспективною: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Я ніколи не використовував дух Boost's Spirit, але готовий спробувати його. Але тільки якщо немає більш простого рішення, яке я не помічаю.


11
Я роздивився boost::spiritрозбір. Це більше для розбору граматик дякую розбору простого формату файлів. Хтось із моєї команди намагався використати його для розбору XML, і це було налагодженням. Тримайтеся подалі від boost::spiritможливості, якщо це можливо.
Кріш

50
Вибачте Кріш, але це жахлива порада. Spirit не завжди є відповідним рішенням, але я його використовую - і продовжую використовувати - успішно в ряді проектів. Порівняно з аналогічними інструментами (Antlr, Lex / yacc тощо), він має значні переваги. Тепер, для розбору CSV, ймовірно, надмірно ...
MattyT

4
@MattyT IMHO spiritдосить важко використовувати для бібліотеки комбінаторів парсерів. Маючи певний (дуже приємний) досвід роботи з (atto)parsecбібліотеками Haskells, я очікував, що він (дух) працює так само добре, але відмовився від нього після боротьби з помилками компілятора 600 рядків.
fho

Відповіді:


296

Якщо ви не переймаєтесь уникненням коми та нового рядка,
і ви не можете вбудувати коти та нові рядки в лапки (якщо ви не можете вийти з цього
пункту ...), то його лише про три рядки коду (OK 14 -> Але його лише 15, щоб прочитати весь файл).

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

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

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

Я б просто створив клас, що представляє рядок.
Потім перейдіть до цього об’єкта:

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

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

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

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

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

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

Але за допомогою невеликої роботи ми могли б технічно створити ітератор:

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

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

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

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


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

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

20
перший () наступний (). Що це за Ява! Лише жартую.
Мартін Йорк

4
@DarthVader: накладне широке твердження, яке за своєю широкістю є дурним. Якщо ви хочете уточнити, чому це погано, і тоді, чому ця поганість застосовується в цьому контексті.
Мартін Йорк

12
@DarthVader: Я думаю, що нерозумно робити широкі узагальнення. Код, що працює вище, працює правильно, тому я можу насправді побачити щось не так. Але якщо у вас є якісь конкретні коментарі до вищесказаного, я обов'язково розгляну це в цьому контексті. Але я бачу, як можна дійти до цього висновку, безглуздо дотримуючись набору узагальнених правил для C # та застосувавши його до іншої мови.
Мартін Йорк

5
також, якщо ви зіткнулися з дивними проблемами зв’язування з вищевказаним кодом, оскільки інша бібліотека десь визначає istream::operator>>(наприклад, Eigen), додайте inlineдо декларації оператора, щоб виправити це.
sebastian_k

3
Це найпростіший і найясніший приклад того, як скласти ітераторський клас, який я коли-небудь бачив.
Джанкарло Спортеллі

46

Рішення за допомогою Boost Tokenizer:

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

9
Підвищуючий токенізатор не повністю підтримує повний стандарт CSV, але є кілька швидких вирішень. Дивіться stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Чи потрібно мати на своїй машині всю бібліотеку збільшення або ви можете просто використовувати підмножину їх коду для цього? 256mb здається дуже багато для розбору CSV ..
NPike

6
@NPike: Ви можете використовувати 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

цей вкладений вектор струн є непридатним для сучасних процесорів. Кидає свою здатність кешувати
Ніколаос Джотіс

плюс ви отримали всі ці заяви про перемикання
Ніколаос Джотіс

Верхня відповідь не спрацювала для мене, оскільки я перебуваю на старшому компіляторі. Ця відповідь спрацювала, векторна ініціалізація може вимагати цього:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

++ Рядок Інструментарій Бібліотека C (StrTk) має символічний клас сітки , що дозволяє завантажувати дані як з текстових файлів, рядків або символьних буферів і розбір / обробляти їх в моді ряду стовпців.

Ви можете вказати роздільники рядків та роздільники стовпців або просто використовувати параметри за замовчуванням.

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

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

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

Більше прикладів можна знайти тут


1
Хоча strtk підтримує подвійні котирування полів і навіть знімає навколишні лапки (через options.trim_dquotes = true), він не підтримує видалення подвійних подвійних лапок (наприклад, поле "She said ""oh no"", and left."як c-рядок "She said \"oh no\", and left."). Вам доведеться це зробити самостійно.
чемпіон

1
Під час використання strtkвам також доведеться вручну обробляти поля з подвійним цитуванням, які містять символи нового рядка.
чемпіон

29

Ви можете використовувати Boost Tokenizer за допомогою escape__list_separator.

escapeped_list_separator аналізує набір csv. Boost :: токенізатор

Для цього використовуються лише заголовні файли заголовка токенізатора, не потрібне посилання для збільшення бібліотек.

Ось приклад (див. Розділ CSV-файл з розширенням 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 секунд на моєму 2 ГГц Xeon. Модуль csv стандартної бібліотеки Python аналізує той самий файл приблизно за 0,3 секунди.
Роб Смоллшир

@Rob, що цікаво - що робить csv Python по-різному?
tofutim

1
@RobSmallshire - це простий приклад коду, не високопродуктивний. Цей код робить копії всіх полів у рядку. Для більшої продуктивності ви використовуєте різні параметри і повертаєте лише посилання на поля в буфері, а не робите копії.
stefanB

29

Не зайвим буде використовувати 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-аналізатори представлені десь посередині і торкаються різних методик використання Spirit. Сформований код такий же щільний, як і рукописний код. Перевірте створений асемблер!


18
Насправді це надмірно, час компіляції величезний, і робить використання Spirit для простих "завдань мікроаналізу" нерозумним.
Гердінер

13
Також я хочу зазначити, що наведений вище код не розбирає CSV, він просто аналізує діапазон типу вектора, розділеного комами. Він не обробляє цитати, різні типи стовпців тощо. Коротше 19 голосів за те, що відповідає на питання, мені здається трохи підозрілим.
Гердінер

9
@Gerdiner Дурниці. Час компіляції для маленьких парсерів не так вже й великий, але це також не має значення, оскільки ви вставляєте код у свій власний блок компіляції та компілюєте його один раз . Тоді вам потрібно лише пов’язати це, і це настільки ефективно, наскільки це виходить. Що стосується вашого іншого коментаря, то існує стільки діалектів CSV, скільки є процесори для нього. Цей звичайно не дуже корисний діалект, але його можна тривіально розширити, щоб обробити цитовані значення.
Конрад Рудольф

11
@konrad: Просто включення "#include <boost / spirit / include / qi.hpp>" у порожній файл із лише основним і нічого іншого займає 9,7сек. з MSVC 2012 на corei7, що працює на 2.ghz. Це непотрібне здуття. Прийнята відповідь складається на відстані менше 2 секунд на одній машині, я б не хотів уявити, скільки часу потрібен приклад "Boost.Spirit" для складання.
Гердінер

11
@Gerdiner Я повинен погодитися з вами накладними способами використання духу для чогось такого простого, як обробка відеозаписів занадто велика.

18

Якщо ви DO піклуватися про розборі CSV правильно, це буде зробити ... відносно повільно , так як він працює один символ за один раз.

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

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

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

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

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

          aChar++;
       }

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

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

АФАКТИЧНО це не буде правильно поводитись із вбудованими цитатами (напр., "Цей рядок має" "вбудовані лапки" "", "foo", 1))
Джеремі Фріснер,

14

Використовуючи Boke Tokenizer escape_list_separator для файлів CSV, слід пам’ятати про наступне:

  1. Для цього потрібен символ утечі (за замовчуванням косою рисою - \)
  2. Для цього потрібен роздільник / сепаратор-символ (кома за замовчуванням -,)
  3. Для нього потрібен символ цитати (типова цитата - ")

У форматі CSV, визначеному wiki, зазначено, що поля даних можуть містити роздільники в лапках (підтримується):

1997, Ford, E350, "Супер, розкішна вантажівка"

У форматі CSV, визначеному wiki, зазначено, що одиничні лапки повинні оброблятися подвійними лапками (escape_list_separator позбавить усіх символів цитат):

1997, Ford, E350, "Супер", "розкішний" "вантажівка"

Формат CSV не визначає, що будь-які символи зворотної косої риски повинні бути позбавлені (escape__list_separator зніме всі символи втечі).

Можливий обхід, щоб виправити типову поведінку підсилювача утекло_list_separator:

  1. Спочатку замініть усі символи зворотної косої риски (\) двома символами зворотної косої риски (\\), щоб вони не знімалися.
  2. По-друге, замініть усі подвійні лапки ("") одним символом зворотної косої риски та цитатою (\ ")

Ця обробка має побічний ефект, що порожні поля даних, які представлені подвійною цитатою, будуть перетворені в маркер з однією цитатою. Під час ітерації через лексеми необхідно перевірити, чи маркер є одноцитатою, і ставитися до неї як до порожнього рядка.

Не дуже, але це працює, доки в цитатах немає нових рядків.


8

Можливо, ви захочете переглянути мій проект FOSS CSVfix ( оновлене посилання ), що є редактором потоку CSV, написаним на C ++. Аналізатор CSV не є виграшним, але виконує завдання і весь пакет може робити все, що вам потрібно, не написавши жодного коду.

Дивіться alib / src / a_csv.cpp для аналізатора CSV та csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) для прикладу використання.


Здається чудово ... А як щодо бета-статусу / виробництва?
нейро

Статус знаходиться "в стадії розробки", як пропонують номери версій. Мені дійсно потрібно більше відгуків користувачів перед переходом до версії 1.0. Плюс у мене є ще кілька функцій, які я хочу додати, пов'язані з виробництвом XML від CSV.

Закладка, і я спробую наступного разу, коли мені доведеться зіткнутися з цими чудовими стандартними файлами CSV ...
neuro

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

Я написав аналізатор CSV лише для заголовка . Він добре перевірений, швидкий, підтримує всю специфікацію CSV (цитовані поля, роздільник / термінатор у лапках, невідповідність котирування тощо) та налаштовується для обліку CSV, які не відповідають специфікації.

Конфігурація проводиться через вільний інтерфейс:

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

Парсинг - це лише діапазон, заснований на циклі:

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

using namespace aria::csv;

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

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

1
Приємна робота, але вам потрібно додати ще три речі: (1) зачитувати заголовок (2) забезпечити індексацію полів по імені (3) не перерозподіляти пам'ять у циклі, використовуючи той самий векторний рядок
Максим Ганенко,

@MaksymGanenko я роблю №3. Не могли б ви детальніше зупинитися на №2?
m0meni

1
Дуже корисно отримувати поля не за позицією підряд, а за назвою, вказаним у заголовку (у першому рядку таблиці CSV). Наприклад, я очікую таблицю CSV з полем "Дата", але я не знаю, що таке індекс поля "Дата" підряд.
Максим Ганенко

1
@MaksymGanenko ах я бачу, що ти маєш на увазі. Там є github.com/ben-strasser/fast-cpp-csv-parser, коли ви знаєте стовпці вашого CSV під час компіляції, і це, мабуть, краще, ніж у мене. Я хотів провести аналіз CSV для тих випадків, коли ви хочете використовувати один і той же код для багатьох різних CSV-файлів і не знаєте, як вони виглядають раніше часу. Тож я, мабуть, не додаватиму №2, але колись додам №1.
m0meni

5

Ще одну бібліотеку вводу-виводу CSV можна знайти тут:

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

Ще одне рішення, подібне до відповіді Локі Астарі , в С ++ 11. Тут є рядки std::tupleзаданого типу. Код сканує один рядок, потім сканує до кожного роздільника, а потім перетворює та скидає значення безпосередньо в кортеж (з трохи коду шаблону).

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

Аванси:

  • досить чистий і простий у використанні, лише C ++ 11.
  • автоматичне перетворення типу в std::tuple<t1, ...>через 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, які я в основному використовую, є свої конвенції. Ключове слово "inline" має бути лише стимулом.
Spak

4

Ось ще одна реалізація аналізатора CSV Unicode (працює з wchar_t). Я написав частину, а Джонатан Леффлер написав решту.

Примітка. Цей аналізатор спрямований на максимальну репликацію поведінки Excel, зокрема при імпорті зламаних або неправильно сформованих файлів CSV.

Це оригінальне запитання - Розбір файлу CSV з багаторядковими полями та уникнутими подвійними лапки

Це код як SSCCE (Короткий, самодостатній, правильний приклад).

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

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

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

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

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

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

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

    return p;
}

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

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

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

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

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

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

    return 0;
}

3

Мені потрібна була проста у користуванні бібліотека 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. Крім того, він може пропустити останній рядок даних. На жаль, ваша вія не є надійною.
Максим Ганенко

1
Дякуємо за відгук @MaksymGanenko Я виправив помилку "останнього рядка даних" для кінцевих рядків без розриву кінцевої лінії. Щодо іншого згаданого питання - "заголовки з порожніми мітками" - я не впевнений, на що йдеться? Бібліотека повинна обробляти порожні мітки (як цитовані, так і не цитовані). Він також може читати CSV без рядка / стовпця заголовка, але тоді він вимагає від користувача вказати це (заголовок ідентифікатора -1 та ідентифікатор заголовка рядка -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 переміщує його по одному рядку.
Мартін Шутт

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

ви можете токенізувати дані та зберігати в масиві char, а пізніше використовувати функцію atoi () тощо для відповідних перетворень

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

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

[^,], ^ -вертає логіку, означає відповідність будь-якій рядку, яка не містить кома, а потім останнього, каже, що відповідає кома, яка закінчила попередній рядок.


2

Перше, що вам потрібно зробити, це переконатися, що файл існує. Для цього вам просто потрібно спробувати відкрити потік файлів на шляху. Після того, як ви відкрили потік файлів, використовуйте stream.fail (), щоб побачити, чи він працював, як очікувалося, чи ні.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Ви також повинні переконатися, що наданий файл є правильним типом файлу. Для цього вам потрібно проглянути шлях до файлу, доки не знайдете розширення. Після розширення файлу переконайтеся, що це файл .csv.

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

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

string extension;

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

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

Ця функція поверне розширення файлу, яке буде використано пізніше у повідомленні про помилку.

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

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

string extension;

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

return extension;
}

Ця функція насправді викликатиме перевірки помилок, створені вище, а потім аналізує файл.

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

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

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

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

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

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

2

Ви повинні відчувати гордість, коли використовуєте щось таке красиве boost::spirit

Ось моя спроба аналізатора (майже) відповідати специфікаціям CSV на цьому посиланні CSV-специфікації (мені не потрібні розриви рядків у полях. Також пробіли навколо коми відкидаються).

Після подолання шокуючого досвіду очікування 10 секунд для складання цього коду :) ви можете сісти і насолоджуватися.

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

#include <iostream>
#include <string>

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

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

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

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

};

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

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

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

        if (str.empty())
            break;

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

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

    return 0;
}

Збірка:

make csvparser

Тест (приклад, викрадений з Вікіпедії ):

./csvparser
Enter CSV lines [empty] to quit

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

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

2

Це рішення виявляє ці 4 випадки

повний клас у

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

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

Він зчитує символ файлу за символом і зчитує по одному рядку у вектор (з рядків), тому підходить для дуже великих файлів.

Використання є

Ітерація, поки порожній рядок не повернеться (кінець файлу). Рядок - вектор, де кожен запис - це стовпець CSV.

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

декларація класу

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

впровадження

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

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

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

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

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

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

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

    default:
      column += c;
      break;
    }
  }

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

1

Ви також можете поглянути на можливості Qtбібліотеки.

Він підтримує регулярні вирази, і клас QString має приємні методи, наприклад, split()повернення QStringList, список рядків, отриманих шляхом розщеплення вихідної рядки за допомогою наданого роздільника. Повинно вистачити для CSV-файлу ..

Для отримання стовпця із заданим іменем заголовка я використовую наступне: c ++ спадкування Qt проблема qstring


це не вирішить коми в лапках
Ізе

1

Якщо ви не хочете мати справу з включенням прискорення у ваш проект (він значно великий, якщо все, для чого ви збираєтесь його використовувати, - це аналіз CSV ...)

Мені пощастило з розбором CSV тут:

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

Він обробляє цитовані поля, але не обробляє вбудовані \ n символи (що, мабуть, добре для більшості застосувань).


1
Чи не повинен компілятор викреслити все несуттєве?
tofutim

1

Це стара тема, але вона все ще знаходиться у верхній частині результатів пошуку, тому я додаю своє рішення за допомогою std :: stringstream та простого методу заміни рядків Івом Баумесом, який я знайшов тут.

Наступний приклад буде читати файл за рядком, ігнорувати рядки коментарів, починаючи з //, і аналізувати інші рядки в комбінацію рядків, ints та doublels. 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

Для чого це варто, ось моя реалізація. Він стосується введення вставки, але його можна легко налаштувати на рядок. Він не обробляє новий рядок у полях (як і мій додаток, але додавати його підтримку не надто складно), і він не відповідає "\ 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;}
        }
    }


}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.