C ++でCSVファイルデータを読み取って操作するにはどうすればよいですか? [複製]
質問
この質問にはすでに回答があります:
かなり一目瞭然、グーグルを試してみて、恐ろしい専門家の交換をたくさんもらいましたが、ここでも検索できませんでした。オンラインチュートリアルまたは例が最適です。ありがとう。
解決
あなたが本当にやっていることが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処理を2つの操作と考えてください。
// 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ファイル)ではなく、問題(顧客)にとって意味のある形式で表されます。
- バルクSQLインポート/エクスポート、Excel / OOスプレッドシートファイル、またはHTML
<table>
レンダリングなど、他のデータ形式用のアダプタを簡単に追加できます。 - メモリフットプリントは小さくなる可能性があります(相対
sizeof(Customer)
と1行のバイト数に依存します)。 -
CSVReader
およびCSVWriter
は、パフォーマンスや機能を損なうことなく、メモリ内モデル(Nelsonなど)の基盤として再利用できます。逆は当てはまりません。
他のヒント
詳細情報が役立ちます。
ただし、最も単純な形式:
#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!!!!
}
}
}
この質問もご覧ください: C ++のCSVパーサー
Boost Tokenizerライブラリ、特にエスケープリスト区切り
私は多くのCSVファイルを使用してきました。アドバイスを追加したい:
1-ソース(Excelなど)によっては、コンマまたはタブがフィールドに埋め込まれる場合があります。通常、ルールは「<!> quot;ボストン、マサチューセッツ州02346 <!> quot;」のように、フィールドが二重引用符で区切られるため、「保護」されます。
2-一部のソースでは、すべてのテキストフィールドが二重引用符で区切られません。他のソースはそうします。その他は、数値であってもすべてのフィールドを区切ります。
3-ダブルクォートを含むフィールドは、通常、埋め込まれたダブルクォートを2倍にします(フィールド自体は、<!> quot; George <!> quot; <!> quot; Babe <!>のように、ダブルクォートで区切られますquot; <!> quot; Ruth <!> quot;。
4-一部のソースにはCR / LFが埋め込まれます(Excelはその1つです!)。時々、それはただのCRになるでしょう。通常、フィールドは二重引用符で区切られますが、この状況を処理するのは非常に困難です。
これはあなた自身が取り組むための良い練習です:)
ライブラリを3つの部分に分割する必要があります
- CSVファイルの読み込み
- メモリ内のファイルを表現して、変更して読み取ることができるようにします
- CSVファイルをディスクに保存する
つまり、次を含むCSVDocumentクラスの作成を検討しています。
- Load(const char * file);
- Save(const char * file);
- GetBody
次のようにライブラリを使用できるように:
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;
}
}
ブーストトークナイザーを使用してレコードを解析する、詳細についてはこちらをご覧ください。
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 <!> amp;パイク。 CおよびC ++の両方でCSVファイルを解析する例が含まれています。ただし、コードを使用しなくても本を読む価値はあります。
この興味深いアプローチが見つかりました:
引用: CSVtoCは、CSVまたはコンマ区切り値ファイルを入力として受け取り、C構造体としてダンプするプログラムです。
当然、CSVファイルに変更を加えることはできませんが、メモリへの読み取り専用のデータアクセスのみが必要な場合は、機能する可能性があります。