Как я могу читать данные CSV-файла и манипулировать ими на C ++?[дубликат]

StackOverflow https://stackoverflow.com/questions/415515

  •  03-07-2019
  •  | 
  •  

Вопрос

На этот вопрос уже есть ответ здесь:

Все говорит само за себя: я попробовал Google и нашел множество ужасных expertsexchange, я искал и здесь, но безрезультатно.Лучше всего было бы использовать онлайн-учебник или пример.Спасибо, ребята.

Это было полезно?

Решение

Если то, что вы действительно делаете, это манипулируете самим CSV-файлом, ответ Нельсона имеет смысл.Однако я подозреваю, что CSV -это просто артефакт проблемы, которую вы решаете.В C ++ это, вероятно, означает, что у вас есть что-то подобное в качестве вашей модели данных:

struct Customer {
    int id;
    std::string first_name;
    std::string last_name;
    struct {
        std::string street;
        std::string unit;
    } address;
    char state[2];
    int zip;
};

Таким образом, когда вы работаете с набором данных, имеет смысл иметь std::vector<Customer> или std::set<Customer>.

Имея это в виду, представьте свою обработку CSV в виде двух операций:

// if you wanted to go nuts, you could use a forward iterator concept for both of these
class CSVReader {
public:
    CSVReader(const std::string &inputFile);
    bool hasNextLine();
    void readNextLine(std::vector<std::string> &fields);
private:
    /* secrets */
};
class CSVWriter {
public:
    CSVWriter(const std::string &outputFile);
    void writeNextLine(const std::vector<std::string> &fields);
private:
    /* more secrets */
};
void readCustomers(CSVReader &reader, std::vector<Customer> &customers);
void writeCustomers(CSVWriter &writer, const std::vector<Customer> &customers);

Считывайте и записывайте по одной строке за раз, вместо того чтобы сохранять полное представление самого файла в памяти.Есть несколько очевидных преимуществ:

  1. Ваши данные представлены в форме, которая имеет смысл для вашей проблемы (клиенты), а не для текущего решения (CSV-файлы).
  2. Вы можете просто добавить адаптеры для других форматов данных, таких как массовый импорт / экспорт SQL, файлы электронных таблиц Excel / OO или даже HTML <table> рендеринг.
  3. Ваш объем памяти, вероятно, будет меньше (зависит от относительного sizeof(Customer) против.количество байтов в одной строке).
  4. CSVReader и CSVWriter может быть повторно использован в качестве основы для модели в памяти (такой как модель Нельсона) без потери производительности или функциональности.Обратное неверно.

Другие советы

Было бы полезно получить дополнительную информацию.

Но самая простая форма:

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

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

    std::string line;
    while(std::getline(data,line))
    {
        std::stringstream  lineStream(line);
        std::string        cell;
        while(std::getline(lineStream,cell,','))
        {
            // You have a cell!!!!
        }
    }
 }

Также смотрите этот вопрос: Анализатор CSV на C ++

Вы можете попробовать библиотеку Boost Tokenizer, в частности Экранированный Разделитель списка

В свое время я работал с большим количеством CSV-файлов.Я хотел бы добавить совет:

1 - В зависимости от источника (Excel и т.д.) в поле могут быть встроены запятые или табуляции.Обычно правило заключается в том, что они будут "защищены", потому что поле будет заключено в двойные кавычки, как в "Boston, MA 02346".

2 - Некоторые источники не будут заключать все текстовые поля в двойные кавычки.Это сделают другие источники.Другие будут разделять все поля, даже цифры.

3 - Поля, содержащие двойные кавычки, обычно удваивают встроенные двойные кавычки (а само поле ограничивается двойными кавычками, как в "George ", "Babe", "Ruth".

4 - Некоторые источники будут встраивать CR / LFS (Excel - один из них!).Иногда это будет просто ЧЕК.Обычно поле будет заключено в двойные кавычки, но с этой ситуацией очень трудно справиться.

Это хорошее упражнение для работы над собой :)

Вам следует разбить свою библиотеку на три части

  • Загрузка CSV-файла
  • Представление файла в памяти, чтобы вы могли изменять его и читать
  • Сохранение CSV-файла обратно на диск

Итак, вы смотрите на написание класса CSVDocument, который содержит:

  • Загрузить (файл const char*);
  • Сохранить (файл с постоянным символом *);
  • Получить тело

Чтобы вы могли использовать свою библиотеку следующим образом:

CSVDocument doc;
doc.Load("file.csv");
CSVDocumentBody* body = doc.GetBody();

CSVDocumentRow* header = body->GetRow(0);
for (int i = 0; i < header->GetFieldCount(); i++)
{
    CSVDocumentField* col = header->GetField(i);
    cout << col->GetText() << "\t";
}

for (int i = 1; i < body->GetRowCount(); i++) // i = 1 so we skip the header
{
    CSVDocumentRow* row = body->GetRow(i);
    for (int p = 0; p < row->GetFieldCount(); p++)
    {
        cout << row->GetField(p)->GetText() << "\t";
    }
    cout << "\n";
}

body->GetRecord(10)->SetText("hello world");

CSVDocumentRow* lastRow = body->AddRow();
lastRow->AddField()->SetText("Hey there");
lastRow->AddField()->SetText("Hey there column 2");

doc->Save("file.csv");

Что дает нам следующие интерфейсы:

class CSVDocument
{
public:
    void Load(const char* file);
    void Save(const char* file);

    CSVDocumentBody* GetBody();
};

class CSVDocumentBody
{
public:
    int GetRowCount();
    CSVDocumentRow* GetRow(int index);
    CSVDocumentRow* AddRow();
};

class CSVDocumentRow
{
public:
    int GetFieldCount();
    CSVDocumentField* GetField(int index);
    CSVDocumentField* AddField(int index);
};

class CSVDocumentField
{
public:
    const char* GetText();
    void GetText(const char* text);
};

Теперь вам просто нужно заполнить пробелы отсюда :)

Поверьте мне, когда я говорю это - инвестирование вашего времени в изучение того, как создавать библиотеки, особенно те, которые связаны с загрузкой, манипулированием и сохранением данных, не только устранит вашу зависимость от существования таких библиотек, но и сделает вас всесторонне лучшим программистом.

:)

Редактировать

Я не знаю, как много вы уже знаете о манипулировании строками и синтаксическом анализе;так что, если вы застряли, я был бы рад помочь.

Вот некоторый код, который вы можете использовать.Данные из csv хранятся внутри массива строк.Каждая строка представляет собой массив строк.Надеюсь, это поможет.

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
typedef std::string String;
typedef std::vector<String> CSVRow;
typedef CSVRow::const_iterator CSVRowCI;
typedef std::vector<CSVRow> CSVDatabase;
typedef CSVDatabase::const_iterator CSVDatabaseCI;
void readCSV(std::istream &input, CSVDatabase &db);
void display(const CSVRow&);
void display(const CSVDatabase&);
int main(){
  std::fstream file("file.csv", std::ios::in);
  if(!file.is_open()){
    std::cout << "File not found!\n";
    return 1;
  }
  CSVDatabase db;
  readCSV(file, db);
  display(db);
}
void readCSV(std::istream &input, CSVDatabase &db){
  String csvLine;
  // read every line from the stream
  while( std::getline(input, csvLine) ){
    std::istringstream csvStream(csvLine);
    CSVRow csvRow;
    String csvCol;
    // read every element from the line that is seperated by commas
    // and put it into the vector or strings
    while( std::getline(csvStream, csvCol, ',') )
      csvRow.push_back(csvCol);
    db.push_back(csvRow);
  }
}
void display(const CSVRow& row){
  if(!row.size())
    return;
  CSVRowCI i=row.begin();
  std::cout<<*(i++);
  for(;i != row.end();++i)
    std::cout<<','<<*i;
}
void display(const CSVDatabase& db){
  if(!db.size())
    return;
  CSVDatabaseCI i=db.begin();
  for(; i != db.end(); ++i){
    display(*i);
    std::cout<<std::endl;
  }
}

Использование boost tokenizer для анализа записей, смотрите здесь для получения более подробной информации.

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

    /// do something with the record
    if (vec.size() < 3) continue;

    copy(vec.begin(), vec.end(),
         ostream_iterator<string>(cout, "|"));

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

Посмотри на 'Практика программирования' (TPOP) от Kernighan & Pike.Он включает в себя пример синтаксического анализа CSV-файлов как на C, так и на C ++.Но прочитать книгу стоило бы, даже если вы не используете код.

(Предыдущий URL: http://cm.bell-labs.com/cm/cs/tpop/)

Я нашел этот интересный подход:

Утилита преобразования CSV в C-структуру

Цитата:CSVtoC - это программа, которая принимает файл CSV или значений, разделенных запятыми, в качестве входных данных и выводит его в виде структуры C.

Естественно, вы не можете вносить изменения в CSV-файл, но если вам просто нужен доступ к данным в памяти только для чтения, это может сработать.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top