Comment puis-je lire et manipuler des données de fichier CSV en C ++? [dupliquer]

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

  •  03-07-2019
  •  | 
  •  

Question

    

Cette question a déjà une réponse ici:

         

Plutôt explicite, j’ai essayé Google et obtenu un grand nombre des experts tant redoutés, j’ai cherché ici aussi en vain. Un tutoriel ou un exemple en ligne serait préférable. Merci les gars.

Était-ce utile?

La solution

Si vous manipulez réellement un fichier CSV, la réponse de Nelson est logique. Cependant, je soupçonne que le CSV est simplement un artefact du problème que vous résolvez. En C ++, cela signifie probablement que vous avez quelque chose comme ceci comme modèle de données:

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

Ainsi, lorsque vous travaillez avec une collection de données, il est logique d'avoir std::vector<Customer> ou std::set<Customer>.

Dans cet esprit, pensez à la gestion de votre fichier CSV comme deux opérations:

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

Lire et écrire une seule ligne à la fois, plutôt que de conserver une représentation complète en mémoire du fichier lui-même. Il y a quelques avantages évidents:

  1. Vos données sont représentées sous une forme qui convient à votre problème (clients) plutôt qu'à la solution actuelle (fichiers CSV).
  2. Vous pouvez facilement ajouter des adaptateurs pour d'autres formats de données, tels que l'importation / exportation SQL en bloc, les fichiers de feuille de calcul Excel / OO ou même un rendu HTML <table>.
  3. Votre empreinte mémoire sera probablement plus petite (dépend du nombre relatif sizeof(Customer) par rapport au nombre d'octets d'une seule ligne).
  4. CSVReader et CSVWriter peuvent être réutilisés comme base pour un modèle en mémoire (tel que celui de Nelson) sans perte de performance ou de fonctionnalité. L'inverse n'est pas vrai.

Autres conseils

Plus d'informations seraient utiles.

Mais la forme la plus simple:

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

Voir aussi cette question: Analyseur CSV en C ++

Vous pouvez essayer la bibliothèque Boost Tokenizer, en particulier la Escaped Séparateur de liste

J'ai travaillé avec beaucoup de fichiers CSV à mon époque. J'aimerais ajouter le conseil:

1 - Selon la source (Excel, etc.), des virgules ou des tabulations peuvent être incorporés dans un champ. En règle générale, la règle est qu'ils seront "protégés" car le champ sera délimité par des guillemets doubles, comme dans & "Boston, MA 02346 &";.

.

2 - Certaines sources ne délimitent pas tous les champs de texte entre guillemets. D'autres sources le feront. D'autres délimiteront tous les champs, même numériques.

3 - Les champs contenant des guillemets doubles sont généralement doublés (le champ lui-même étant délimité par des guillemets, comme dans & "George &"; & "Babe & "! &"; Ruth & ";

4 - Certaines sources incorporeront des CR / LF (Excel en est un!). Parfois, ce sera juste un CR. Le champ sera généralement délimité par des guillemets, mais cette situation est très difficile à gérer.

C’est un bon exercice pour vous-même:)

Vous devez diviser votre bibliothèque en trois parties

  • Chargement du fichier CSV
  • Représenter le fichier en mémoire afin que vous puissiez le modifier et le lire
  • Sauvegarde du fichier CSV sur le disque

Vous envisagez donc d'écrire une classe CSVDocument qui contient:

  • Charger (fichier const char *);
  • Enregistrer (fichier const char *);
  • GetBody

Pour que vous puissiez utiliser votre bibliothèque comme ceci:

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

Ce qui nous donne les interfaces suivantes:

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

Il ne vous reste plus qu'à remplir les blancs à partir d'ici:

Croyez-moi quand je dis cela - investir votre temps à apprendre à créer des bibliothèques, en particulier celles qui traitent du chargement, de la manipulation et de la sauvegarde des données, vous évitera non seulement de dépendre de l'existence de telles bibliothèques, mais un meilleur programmeur complet.

:

MODIFIER

Je ne sais pas ce que vous savez déjà sur la manipulation et l'analyse des chaînes. donc si vous êtes coincé, je serais heureux de vous aider.

Voici du code que vous pouvez utiliser. Les données du fichier csv sont stockées dans un tableau de lignes. Chaque ligne est un tableau de chaînes. J'espère que cela vous aidera.

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

Utilisation de tokenizer boost pour analyser les enregistrements , voir ici pour plus de détails .

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

Regardez ' La pratique de la programmation ' (TPOP) par Kernighan & amp; Brochet. Il comprend un exemple d'analyse de fichiers CSV en C et C ++. Mais cela vaut la peine de lire le livre même si vous n’utilisez pas le code.

(URL précédente: http://cm.bell-labs.com/ cm / cs / tpop / )

J'ai trouvé cette approche intéressante:

Utilitaire de structure CSV en C

Citation: CSVtoC est un programme qui prend en entrée un fichier CSV ou de valeurs séparées par des virgules et le dépose en tant que structure C.

Naturellement, vous ne pouvez pas modifier le fichier CSV, mais si vous avez simplement besoin d'un accès en lecture seule aux données en mémoire, cela pourrait fonctionner.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top