Come posso leggere e manipolare i dati dei file CSV in C ++? [duplicare]

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

  •  03-07-2019
  •  | 
  •  

Domanda

    

Questa domanda ha già una risposta qui:

         

Abbastanza autoesplicativo, ho provato google e ho avuto un sacco di temuti scambi di esperti, ho cercato anche qui senza alcun risultato. Un tutorial o esempio online sarebbe il migliore. Grazie ragazzi.

È stato utile?

Soluzione

Se quello che stai veramente facendo è manipolare un file CSV stesso, la risposta di Nelson ha senso. Tuttavia, il mio sospetto è che il CSV sia semplicemente un artefatto del problema che stai risolvendo. In C ++, ciò probabilmente significa che hai qualcosa del genere come modello di dati:

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

Pertanto, quando si lavora con una raccolta di dati, ha senso avere std::vector<Customer> o std::set<Customer>.

Con questo in mente, pensa alla tua gestione CSV come a due operazioni:

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

Leggi e scrivi una singola riga alla volta, invece di mantenere una rappresentazione in memoria completa del file stesso. Ci sono alcuni ovvi benefici:

  1. I tuoi dati sono rappresentati in una forma che ha senso per il tuo problema (clienti), piuttosto che la soluzione corrente (file CSV).
  2. Puoi aggiungere banalmente adattatori per altri formati di dati, come importazione / esportazione di massa SQL, file di fogli di calcolo Excel / OO o persino un rendering HTML <table>.
  3. È probabile che il tuo footprint di memoria sia più piccolo (dipende dal sizeof(Customer) relativo rispetto al numero di byte in una singola riga).
  4. CSVReader e CSVWriter possono essere riutilizzati come base per un modello in memoria (come quello di Nelson) senza perdita di prestazioni o funzionalità. Il contrario non è vero.

Altri suggerimenti

Ulteriori informazioni sarebbero utili.

Ma la forma più semplice:

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

Vedi anche questa domanda: parser CSV in C ++

Puoi provare la libreria Boost Tokenizer, in particolare Escaped Separatore elenco

Ho lavorato con molti file CSV ai miei tempi. Vorrei aggiungere il consiglio:

1 - A seconda dell'origine (Excel, ecc.), le virgole o le schede possono essere incorporate in un campo. Di solito, la regola è che saranno "protetti" perché il campo sarà delimitato da virgolette doppie, come in & Quot; Boston, MA 02346 & Quot ;.

2 - Alcune fonti non racchiuderanno virgolette doppie di tutti i campi di testo. Altre fonti lo faranno. Altri delimiteranno tutti i campi, anche i numeri.

3 - I campi contenenti virgolette doppie di solito raddoppiano le doppie virgolette incorporate (e il campo stesso delimitato da virgolette doppie, come in " George " " Babe " " Ruth " ;.

4 - Alcune fonti incorporeranno CR / LF (Excel è uno di questi!). A volte sarà solo un CR. Il campo sarà solitamente delimitato da virgolette doppie, ma questa situazione è molto difficile da gestire.

Questo è un buon esercizio su cui lavorare :)

Dovresti suddividere la tua libreria in tre parti

  • Caricamento del file CSV
  • Rappresenta il file in memoria in modo da poterlo modificare e leggerlo
  • Salvataggio del file CSV su disco

Quindi stai cercando di scrivere una classe CSVDocument che contiene:

  • Carica (const char * file);
  • Salva (const char * file);
  • getBody

Per poter utilizzare la tua libreria in questo modo:

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

Che ci dà le seguenti interfacce:

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

Ora devi solo riempire gli spazi vuoti da qui :)

Credimi quando dico questo: investire il tuo tempo nell'imparare a creare biblioteche, specialmente quelle che si occupano di caricamento, manipolazione e salvataggio dei dati, non solo rimuoverà la tua dipendenza dall'esistenza di tali biblioteche, ma ti farà anche un programmatore migliore a tutto tondo.

:)

Modifica

Non so quanto tu sappia già sulla manipolazione e l'analisi delle stringhe; quindi se rimani bloccato sarei felice di aiutarti.

Ecco un codice che puoi usare. I dati dal CSV sono memorizzati in una matrice di righe. Ogni riga è una matrice di stringhe. Spero che questo aiuti.

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

Utilizzo del tokenizer boost per analizzare i record , vedi qui per maggiori dettagli .

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

Guarda ' The Practice of Programming ' (TPOP) di Kernighan & amp; Luccio. Include un esempio di analisi dei file CSV in C e C ++. Ma varrebbe la pena leggere il libro anche se non si utilizza il codice.

(URL precedente: http://cm.bell-labs.com/ cm / cs / tpop / )

Ho trovato questo approccio interessante:

Utilità struttura da CSV a C

Citazione: CSVtoC è un programma che accetta come input un file di valori CSV o separato da virgole e lo scarica come struttura C.

Naturalmente, non è possibile apportare modifiche al file CSV, ma se si necessita solo dell'accesso in sola lettura ai dati, potrebbe funzionare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top