Manipolazione dati in C++
CSV
Un testo in formato CSV (comma separated values) consiste in testo semplice che però è formattato in modo tale da rappresentare informazioni in maniera tabellare.
Ogni riga rappresenta un insieme di informazioni tra loro collegate che insieme costituiscono un record. All’interno di un record sono presenti diversi campi, ovvero dati separati da virgola1
E’ inoltre opzionalmente prevista una riga di intestazione coi nomi dei campi.
Qui un esempio:
Nome,Cognome,Età,Email,Città
Luca,Rossi,30,luca.rossi@email.com,Roma
Giulia,Bianchi,35,giulia.bianchi@email.com,Milano
Marco,Verdi,43,marco.verdi@email.com,Torino
Elena,Neri,30,elena.neri@email.com,Firenze
Francesco,Conti,37,francesco.conti@email.com,Napoli
Sara,Gallo,32,sara.gallo@email.com,Bologna
Alessandro,Ferri,46,alessandro.ferri@email.com,Genova
Martina,Greco,34,martina.greco@email.com,Bari
Daniele,Romano,28,daniele.romano@email.com,Palermo
Chiara,Costa,29,chiara.costa@email.com,Venezia
Come si vede dall’esempio la prima riga contiene i nomi dei campi (separati da virgola) le successive i dati veri e propri organizzati per righe e per ciascuna riga i valori sono separati da virgola. Ogni riga rappresenta un record (in questo caso il record anagrafico di una persona) ed ogni elemento della riga racchiuso tra virgole è chiamato campo (in questo caso nome, cognome, indirizzo, ecc,).
I file CSV sono una esportazione di fogli di calcolo (ad esempio Excel) e sono un sistema per memorizzare piccole o grandi quantità di dati in forma tabellare.
Tramite il C++ possiamo caricare questi dati in strutture in memoria, per svolgere vari tipi di elaborazioni automatiche, e possiamo esportare di nuovo in formato CSV.
Csv e matrici
Per importare un file CSV in una struttura dati useremo funzione Strtok attraverso la quale separaremo prima le righe (carattere “\n”) e poi le colonne (“,”).
La prima cosa che dobbiamo fare è caricare usando ifstream, le singole righe del CSV.
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
int main()
{
ifstream file("file.txt");
char row[200]; // ipotizziamo una riga di 200 caratteri
while (file.getline(row, 200)) {
cout << row << endl;
};
return 0;
}
Proviamo adesso a memorizzare l’intero csv in una matrice. Vediamo i passaggi:
- DImensioniamo la matrice, che avrà 3 dimensioni:
char csv[numero_righe][numero_colonne][dimensione_stringa_campo];
Ovvero:
- il numero delle righe (record) del csv: va ipotizzato un valore massimo, oppure come in questo caso possiamo, prima di eseguire la lettura vera e propria, leggere il file per contare le righe;
- il numero delle colonne (campi) del csv: questo numero è noto conoscendo già il CSV, ad esempio sono 5 nel CSV sopra indicato;
- la dimensione del campo: anche qui bisogna fare una ipotesi, nel nostro caso è ragionevole ipotizzare stringhe di 30 caratteri, ma è bene usare una costante di precompilazione.
2. Scriviamo quindi la funzione principale, che legge il file riga per riga, e poi divide la riga (variabile line) in colonne con strtok, nella variabile csv.
3. Scriviamo poi una funzione di comodo per stampare l’array csv
Vediamo il codice:
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
#define FIELD_SIZE 30
int count_rows(char filename[]) {
int count = 0;
ifstream file(filename);
char line[200]; // ipotizziamo una riga di 200 caratteri
while (file.getline(line, 200)) {
count++;
};
file.close();
return count;
}
void print(char csv[][5][FIELD_SIZE], int num_rows) {
for (int i=0; i<num_rows; i++) {
cout << i << ": ";
for (int j=0; j<5; j++) {
cout << csv[i][j] << " - ";
}
cout << endl;
}
}
int main()
{
char filename[] = "file.txt";
int num_rows = count_rows(filename) - 1; //togliamo la prima riga dal conteggio
char csv[num_rows][5][FIELD_SIZE];
int row = 0;
char line[200]; // ipotizziamo una riga di 200 caratteri
ifstream file(filename);
file.getline(line, 200); // leggiamo la prima riga, per saltarla, è l'intestazione
while (file.getline(line, 200)) {
char *token = strtok(line, ",");
int col = 0;
while (token) {
strcpy(csv[row][col], token);
token = strtok(NULL, ",");
col++;
}
row++;
};
print(csv, num_rows);
file.close();
return 0;
}
Con questo sistema possiamo quindi importare qualsiasi CSV e memorizzarlo in una matrice.
CSV e struct
E’ evidente tuttavia che la matrice, per molti tipi di utilizzo, non è la struttura appropriata, mentre la struct riesce a definire meglio i compi. Proviamo quindi a creare una struttura adatta al CSV ed importare i dati.
Vediamo il codice:
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
struct Person {
char first_name[15];
char last_name[15];
int age;
char email[30];
char city[15];
};
int count_rows(char filename[]) {
int count = 0;
ifstream file(filename);
char line[200]; // ipotizziamo una riga di 200 caratteri
while (file.getline(line, 200)) {
count++;
};
file.close();
return count;
}
void print(Person persons[], int num_rows) {
for (int i=0; i<num_rows; i++) {
cout << i << ": ";
cout << "Nome: " << persons[i].first_name << " ";
cout << "Cognome: " << persons[i].last_name << " ";
cout << "Età: " << persons[i].age << " ";
cout << "Email: " << persons[i].email << " ";
cout << "Città: " << persons[i].city << " ";
cout << endl;
}
}
int main()
{
char filename[] = "file.txt";
int num_rows = count_rows(filename) - 1; //togliamo la prima riga dal conteggio
Person persons[num_rows];
int row = 0;
char line[200]; // ipotizziamo una riga di 200 caratteri
ifstream file(filename);
file.getline(line, 200); // leggiamo la prima riga, per saltarla, è l'intestazione
while (file.getline(line, 200)) {
char *token = strtok(line, ",");
strcpy(persons[row].first_name, token);
token = strtok(NULL, ",");
strcpy(persons[row].last_name, token);
token = strtok(NULL, ",");
persons[row].age = atoi(token);
token = strtok(NULL, ",");
strcpy(persons[row].email, token);
token = strtok(NULL, ",");
strcpy(persons[row].city, token);
row++;
};
print(persons, num_rows);
file.close();
return 0;
}
In questo caso abbiamo una indubbia comodità: i dati smettono di essere celle di una matrice, ma sono strutturati in una informazione, la persona, che ci permette una analisi più approfondita. Inoltre è possibile dare un tipo di dati corretto ai singoli campi, ad esempio adesso l’età è un numero intero e non più una stringa.
Esportare in CSV
E’ ovviamente anche possibile esportare in CSV. Per farlo, possiamo usare ofstream.
void save(Person persons[], int start, int end, char filename[]) {
ofstream file(filename);
for (int i=start; i<end; i++) {
file << persons[i].first_name << ",";
file << persons[i].last_name << ",";
file << persons[i].age << ",";
file << persons[i].email << ",";
file << persons[i].city << ",";
file << endl;
}
file.close();
}
Questa funzione, ad esempio, salva nel file indicato i record da start a end.
Qui un esempio di utilizzo (da mettere alla fine del main):
save(persons, 0, 5, "output.csv");
Testare il codice e provare a personalizzarlo.