なまもの備忘録

気になったことをつらつらと書いていきます

(C++)大きさ不明の2次元配列データを2次元vectorに読み込む方法

3/22 17:10

大きさの分からない2次元配列データを2次元vectorに格納する方法がいくら調べても見つからなかったので、分からないなりに調べつつコードを作成してみた。
下記のコードであれば、データが数値のcsvファイルならなんでも読み込めるはず。

2017/10/20
久しぶりにコードを見返したところ、いくつか気になった点があったので修正。

修正点
 任意の数値型のvectorに対してGetdata関数が適用できるようにした。
 using namespace std;を削除した。

2018/2/25
コードを見返したところ、いくつか気になった点が出てきたので修正。

修正点
 引数で指定したファイルが見つからない場合に例外を返却するよう修正。
 引数で受け取ったvectorに副作用でデータを格納するのではなく、関数の返り値として読み込みデータを返却するように変更。
 第2引数で空読みしたい行数を指定できるよう変更。
 ループ変数名と型(int->std::size_t)の修正。

2018/6/4
頂いたコメントを受けて修正。その他仕様に至らぬ点があったため修正。

修正点
 コメントへの返信に記載。
 csvファイルの末尾に空行がある際、dataの行数も空行分大きくなってしまう点を修正。
 csvファイルの各行末に","が存在する場合、行末のデータを重複して読み込んでしまう点を修正。

//大きさ不明の2次元配列データを2次元vectorに読み込むGetdata関数

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <exception>

template<typename Num_type>
std::vector<std::vector<Num_type> >
Getdata(std::string filename, std::size_t ignore_line_num = 0){
    std::ifstream reading_file;
    reading_file.open(filename, std::ios::in);
    if(!reading_file)
        throw std::invalid_argument(filename + std::string("could not be opened."));
    
    std::string reading_line_buffer;
    
    //最初のignore_line_num行を空読みする
    for(std::size_t line = 0; line < ignore_line_num; line++){
        getline(reading_file,reading_line_buffer);
        if(reading_file.eof()) break;
    }
        
    Num_type num;
    char comma;

    std::vector<std::vector<Num_type> > data;
        
    while(std::getline(reading_file, reading_line_buffer)){    
        if(reading_line_buffer.size() == 0) break;
	std::vector<Num_type> temp_data;
	std::istringstream is(reading_line_buffer);
	while(is >> num){
	    temp_data.push_back(num);
	    is >> comma;
	}
	data.push_back(temp_data);
    }

    return data;
}

//動作テスト                                                                      
int main(){
    //読み込みデータ格納先2次元vectorを用意する、今回の数値型はなんでも良い
    std::vector<std::vector<double> > data;

    std::string filename("test.csv");
    
    //Getdata関数を使う    
    //テンプレート引数として読み込みデータの型名を指定(今回はdouble)。
    //第1引数に読み込みたいファイルのカレントディレクトリからのパスを指定する
    std::cout << "reading " << filename << "..." << std::endl;
    data = Getdata<double>(filename);

    for(std::size_t line=0 ;line < data.size() ;line++){
	for(int column=0 ;column < data[line].size() ;column++){
	    std::cout << data[line][column] << " ";
	}
	std::cout << std::endl;
    }
    std::cout << std::endl;

    //最初のn行を読み飛ばす場合には第2引数にnを入れる
    std::size_t n = 3;
    std::cout << "reading " << filename << "..." << std::endl;
    data = Getdata<double>(filename,n);

    for(std::size_t line=0 ;line < data.size() ;line++){
	for(std::size_t column=0 ;column < data[line].size() ;column++){
	    std::cout << data[line][column] << " ";
	}
	std::cout << std::endl;
    }
    std::cout << std::endl;
    
    return 0;
}