もう電線病気は卒業したひとです。
csvで困るのは
” ダブルコーテーションのなかに囲まれた” カラムを持つ とかなんとか実験はそこそこに多分どうさするであろうライブラリを紹介します。
実際にはこのライブラリが読み使えたという報告にすぎません。今日はラピッドCSVを紹介します。
d99kris/rapidcsv: C++ CSV parser library (github.com)
このギットにいろいろはいっていますが、つかうのはrapidscv.h だけです。その他のファイルはつかいません。
実際に読み込んで使えることが大事なので今日はそこまで検証もしてみましょう。
サンプルとしてつかうのはサンプルとして使ってもいいということで公開されてるものです
キャドが出力する部品表で自分が管理するのに使いもしないカラムがあるという体裁を題材として自分はやっていますがそのデータは秘密ですから
今日はざっくりの実験ですからバグももれなくついてきますが ラビットCSV.hの使い方といういみではまあまあのできです。ライブラリとしての性能をはかるものではありません。
僕の計算で数値と文字列の判断がまだいいかげんな問題がのこっています。 そこはチャレンジだとおもってください
いつか適当にこっそりなおします。
大抵の部品表は タイトルということで何らかの表題の文字がはいっています。
そして カラの行があったりして いわゆるマトリクス カラム名称がって部品が入ってたりします。
今日は乱暴にこんなリストから 適当に カラム0 僕のこのプログラムは最初のカラムを0番といういいかたですから
ここから奇数のカラムを削除するという例題をやってみます。
つぶれてよくみえませんが雰囲気としてはこんなものです。
なんらかのヘッダーがあって という雰囲気です。これをよみとります。
std::vector<int> columnsToDelete = {0, 1, 2, 3, 4,11,14,15,16,17,16, 21,22}; // 0-indexed // 削除する列のインデックス
という削除したいカラムナンバー ゼロから始まるインデックスで 消し去るというものを作ってみます
この削除リストは 削除したいカラム 0から始まる だから //0-indexed とコメント 22カラムがないならないだけのことでないものは削除する意味がないだけです。
最初のデータから 不要なものは削除してるのがわかります。
ちょっと別件DLL 今日のブログの内容からは逸脱
MINGWでつくったDLLの話
QT6をコンパイルの道具としてつかっています。 MINGWです。
ただ 僕の失敗なのか、他の装置のDLLのコンパイルはうまくいきませんでした。コンパイルしてすんだのですがそのコンパイルはMINGWの他のプロジェクトにはつくったDLLがよみこめたのですがとある既存の仕組みはよみこみませんでした。
話をCSV読み込みにもどします。
QT6をコンパイルとデバッグのワンステップ動作にはつかいましたが、コンパイルにはQT6の仕組みはつかいませんから出来上がったオブジェクトは QT6install先のフォルダのMINGW/bin フォルダにあるDLLの いくつかを使うだけになります。
パスを切ってあげるといきなり動作するようです。
proファイルの中身について
TEMPLATE = app
CONFIG += console c++20
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += \
main.cpp
こんな感じです。 CONFIG -= app_bundle このコンフィグのマイナスイコールは その機能を削除するという意味だそうです。 CONFIG -= qt
ここで QT独自のライブラリはつかいませんからQTのプラットフォームつかっていますがQTの命令は全くつかえなくなります。
MINGWを単純につかってるだけです。
( ̄∇ ̄;)ハッハッハ
おまちかね rapidcsv.h
さてライブラリとしてのrapidcsv.h の使い方です。コンパイルが通っただけですから あとは個人でやってみてくださいな
std::vector<int> columnsToDelete = {0, 1, 2, 3, 4,11,14,15,16,17,16, 21,22}; // 0-indexed // 削除する列のインデックス
今回は リストにあるカラムを削除するユーティリティです。
原本のCSVを読み込み表示して このリストにあるインデックスのカラムを削除してみたというお題目です。
つかいたいひとはつかってみてください。
小技 バリアントタイプがつかえたので 😃
小枝と書いてもよかったのですが、意味がちがうのでやめました。
using VariantType = std::variant<int, double, std::string>;
これは カラムの内容が文字数値でもいいやってことです。
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <variant>
#include <stdexcept>
#include "rapidcsv.h"
#include <windows.h>
using VariantType = std::variant<int, double, std::string>;
// 文字列を数値に変換する関数
template<typename T>
bool convertTo(const std::string& value, T& result) {
std::istringstream ss(value);
ss >> result;
return !ss.fail();
}
std::string trim(const std::string& str) {
size_t first = str.find_first_not_of(' ');
if (first == std::string::npos) {
return ""; // 空の文字列の場合
}
size_t last = str.find_last_not_of(' ');
return str.substr(first, last - first + 1);
}
// 文字列が数値かどうかを判断する関数
bool isNumber(const std::string& str) {
// 先頭と末尾の空白文字を無視する
std::string trimmed = trim(str);
// 空の文字列は数値ではない
if (trimmed.empty()) {
return false;
}
// 先頭文字が符号かどうかを確認
size_t start = (trimmed[0] == '-' || trimmed[0] == '+') ? 1 : 0;
bool hasDot = false;
// 残りの文字が数値かどうかを確認
for (size_t i = start; i < trimmed.size(); ++i) {
if (trimmed[i] == '.') {
if (hasDot) {
// 2つ以上のドットがある場合は数値ではない
return false;
}
hasDot = true;
} else if (!std::isdigit(trimmed[i])) {
// 数値以外の文字がある場合は数値ではない
return false;
}
}
return true;
}
// CSVファイルの列データを自動判別して取得する関数
std::pair<std::vector<std::string>, std::vector<std::vector<VariantType>>> getColumnData(const rapidcsv::Document& doc) {
std::vector<std::vector<VariantType>> result;
// ヘッダー行を読み込む(Excelの1行目をカラム名として扱う)
std::vector<std::string> headers = doc.GetRow<std::string>(1); // Excelの1行目をカラム名として読み込む
// ヘッダー行が空の場合はエラーをスローする
if (headers.empty()) {
throw std::runtime_error("Header row is empty.");
}
// データ行を処理する
size_t numRows = doc.GetRowCount();
for (size_t i = 2; i < numRows; ++i) { // Excelの2行目からデータ行が始まるので i = 2 から開始
std::vector<std::string> rowData = doc.GetRow<std::string>(i);
std::vector<VariantType> rowVariants;
// 列のデータ型を自動判別して格納する
for (size_t j = 0; j < rowData.size(); ++j) {
const std::string& data = rowData[j];
if (isNumber(data)) {
// まず整数かどうかをチェック
int intValue;
if (convertTo(data, intValue)) {
rowVariants.push_back(intValue);
continue;
}
// 次に浮動小数点数かどうかをチェック
double doubleValue;
if (convertTo(data, doubleValue)) {
rowVariants.push_back(doubleValue);
continue;
}
}
// どちらも数値に変換できない場合はそのまま文字列として格納
rowVariants.push_back(data);
}
// 行のデータを結果に追加する
result.push_back(rowVariants);
}
return { headers, result };
}
// CSVファイルの列データを指定された列を削除して書き出す関数
void writeCSVWithoutColumns(const std::string& filePath, const std::vector<std::string>& columnNames,
const std::vector<std::vector<VariantType>>& data,
const std::vector<int>& columnsToDelete) {
std::ofstream outFile(filePath);
if (!outFile) {
throw std::runtime_error("Failed to open output file.");
}
std::cout << "--------------------------------------------------------------"<<std::endl<<std::flush;;
// ヘッダー行を書き出す
for (size_t i = 0; i < columnNames.size(); ++i) {
if (std::find(columnsToDelete.begin(), columnsToDelete.end(), i) == columnsToDelete.end()) {
if (i > 0) outFile << ","<<std::flush;
outFile << "\"" << columnNames[i] << "\""<<std::flush; // ダブルクォーテーションで囲んで書き出す
if (i > 0) std::cout << ","<<std::flush;
std::cout << "\"" << columnNames[i] << "\""<<std::flush;
}
}
std::cout << std::endl;
outFile << "\n"<<std::flush;
// データを書き出す
for (const auto& row : data) {
for (size_t i = 0; i < row.size(); ++i) {
if (std::find(columnsToDelete.begin(), columnsToDelete.end(), i) == columnsToDelete.end()) {
if (i > 0) outFile << ",";
std::visit([&outFile](auto&& arg) {
if constexpr (std::is_same_v<std::decay_t<decltype(arg)>, std::string>) {
// 文字列の場合、ダブルクォーテーションで囲んで書き出す
outFile << "\"" << arg << "\"";
} else {
outFile << arg;
}
}, row[i]);
}
}
outFile << "\n";
}
}
int main(int argc, char *argv[]) {
try {
const char* userProfile = std::getenv("USERPROFILE");
if (!userProfile) {
throw std::runtime_error("Failed to retrieve user profile.");
}
std::string csvFilePath = std::string(userProfile) + "/OneDrive/Desktop/csvconv/customers-1000.csv";
rapidcsv::Document doc(csvFilePath);// rapidcsv を使って CSV ドキュメントを読み込む
std::vector<std::string> headers = doc.GetColumnNames();
auto [columnNames, columnData] = getColumnData(doc);// CSVファイルの列データとカラム名を自動判別して取得する
std::cout << "Columns: "; // カラム名を表示する
for (const auto& name : columnNames) {
std::cout << name << "\t";
}
std::cout << std::endl;
for (size_t i = 0; i < columnData.size(); ++i) {// データの表示
std::cout << "Row " << i + 1 << ": ";
for (const auto& data : columnData[i]) {
if (std::holds_alternative<int>(data)) {
std::cout << std::get<int>(data) << " (int)\t";
} else if (std::holds_alternative<double>(data)) {
std::cout << std::get<double>(data) << " (double)\t";
} else if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data) << " (string)\t";
}
}
std::cout << std::endl;
}
std::cout << "--------------------------------------------------------------"<<std::endl<<std::flush;
std::vector<int> columnsToDelete = {0, 1, 2, 3, 4,11,14,15,16,17,16, 21,22}; // 0-indexed // 削除する列のインデックス
std::string autoputpath=csvFilePath + "_output.csv";// 削除後のCSVファイルを書き出す
writeCSVWithoutColumns(autoputpath, columnNames, columnData, columnsToDelete);
} catch (const std::exception& ex) {
std::cerr << "Exception: " << ex.what() << std::endl;
}
return 0;
}
解説
using VariantType = std::variant<int, double, std::string>;
バリアントタイプをつかいます
便利いいでしょう。 CSVには文字もあり数値もあります、とりあえず読み込むには文字で読み取るというのはありといえばありですが、バリアントタイプつまり std::variant<int, double, std::string>; バリアントの意味がわかるわからないなどどうでもいいという立場です。 とにかく一緒くたに読み取ってしまえという話でした。
備忘録 忘備録
備忘録 忘備録どっちがただしいのでしょうか? 僕はしりません ( ̄∇ ̄;)ハッハッハ
実際動作させてみよう