こんにちは。寒くなるにつれて頭痛の酷さが増してきている気がするRockinWoolです。運動不足が影響しているのではないかと思って今日は外を歩いてみたり、2年ぶりくらいにカラオケをしてみたり、風呂に長めに入ってみたりしたけれど効果はありませんでした。仕方無いのでC++の備忘録の第二弾をやって少しでも成長を感じていこうと思います。
配列の中身チェッカ
関数のオーバーロードの練習を兼ねて作ったプログラムです。もし引数がvector<int>だったら、その中身を1行で出力します。vector<vector<int>>だったら全要素を順番に表示します。まずは引数がvector<int>だった場合。
void vector_checker(vector<int> vec){
for (int i=0; i< vec.size(); i++){
cout << vec[i] << ",";
}
cout<<endl;
}
解説するほどの場所は無いかもしれませんが、vec.size()で配列の長さを取得しているところが工夫ですかね。次はvector<vector<int>>の場合。
void vector_checker(vector<vector<int>> vec){
for(int i = 0; i < vec.size(); i++){
cout << i << "->";
for(int j = 0; j < vec[i].size(); j++){
cout << vec[i][j];
if(j != vec[i].size()-1)cout << ",";
}
cout << endl;
}
}
同じvector_checker()という名前ですが、引数が2次元だとこちらが呼ばれます。工夫はvec.size()-1の範囲でコロンを打つようにしている部分ですかね。これらのプログラムは挙動が想像しやすいように書けたと思うのでGood jobだと思います。
失敗事例: vectorでは無い場合
次はバグを生んだプログラムの紹介です。下記のプログラムのバグを見つけてみましょう。
#include <vector>
#include <iostream>
using namespace std;
void list_checker(int *listdata){
for (int i=0; i<sizeof(listdata);i++){
cout << listdata[i] << ",";
}
}
int main(){
int listdata[] = {1,2,3,4,5};
list_checker(listdata);
}
このプログラムを回した際の出力は1,2,3,4,5,0,937289472,-494252404,になります。何か知らない数が生まれてきていますね。ちなみにこれをChatGPTに相談してみたところの回答が下記になります。
The issue you’re encountering is related to how arrays are passed as pointers in C++. When you pass an array to a function, it decays into a pointer to its first element. Therefore, using sizeof() within the function won’t give you the actual size of the array; it will give you the size of the pointer.
※あなたが遭遇している問題は、C++で配列がポインタとして渡される方法に関連しています。配列を関数に渡すと、配列はその最初の要素へのポインタに分解されます。したがって、関数内でsizeof()を使用しても、配列の実際のサイズはわかりません。
ChatGPT曰く、関数にポインタで配列を渡したらsizeof()では長さを測れなくなるという仕様があるとのこと。つまり、引数として配列の長さを別に渡してあげるのが良いとのこと。結果として下記のプログラムが正解になります。
#include <vector>
#include <iostream>
using namespace std;
void list_checker(int *listdata,int length){
for (int i=0; i<length;i++){
cout << listdata[i] << ",";
}
}
int main(){
int listdata[] = {1,2,3,4,5};
list_checker(listdata,5);
}
CSV出力のプログラム
今回は実際に使ったプログラムの転記なので引数名が特殊ですが、3つのデータを保存したCSVを作る関数の例になります。CSVといっても通常のファイル出力と何も変わらないのですが。個人的にはstd::ofstream ofs(filename); で通常の標準出力に出力する場合とほぼ変わらない方法でファイル書き込みできることに感動しました。
#include <fstream>
#include <iostream>
using namespace std;
void output2csv_ExistCongestion(int num_nodes, int *Point_of_Exist, int *Point_of_Congestion, std::string filename){
filename = "./data/"+filename+".csv";
std::ofstream ofs(filename);
ofs << "Node_id" << ","<< "point_of_exist" << "," << "point_of_congestion"<<endl;
for (int i=0; i<num_nodes;i++){
ofs << i << ","<< Point_of_Exist[i] << "," << Point_of_Congestion[i]<<endl;
}
ofs.close();
}
CSVファイルに格納された辺情報を読み取るプログラム
これもかなり限定的な関数ですが供養のためにここに置いておきます。
#include <sstream>
#include <fstream>
#include <string.h>
#include <vector>
using namespace std;
// split関数, deliminaterで指定された文字列で区切る
vector<string> split(string& input, char delimiter){
istringstream stream(input);
string field;
vector<string> result;
while (getline(stream, field, delimiter)) {
result.push_back(field);
}
return result;
}
//ファイルからエッジリストを入手する関数
vector<vector<int>> get_edgelist(string path , int num_nodes){
string str;
int from, to;
ifstream ifs(path);
vector<vector<int>> tmp_nodes = vector<vector<int>>(num_nodes);
while(getline(ifs, str)){
// スペース区切りで分割
vector<string> strvec = split(str, ',');
from = stoi(strvec.at(0));
to = stoi(strvec.at(1));
tmp_nodes[from].push_back(to);
}
return tmp_nodes;
}
stoiという関数はstring.hに入っているもので、文字列をintに変換する機能を持っています。strvec.at(0)にはstrvecの最初の要素であるstringが入っているので、それをintに直しているというわけですわね。
供養完了
これらのプログラムはpybind11によってpython側に任せてしまった機能がほとんどです。とはいえ、勉強にはなりましたしファイル入出力は基本ですしで備忘録にしました。完全に自分用ですみませんが、同じように使いたいひとがこの記事にアクセスしてくれることを願い、ここまでにしておこうと思います。ここまで観てくださってありがとうございました。