C++ - CSV データ読み込み!
Updated:
かつて、 C++ で CSV データを読み込む方法について記録しました。
今回も同様のことをしてみましたが、ある(古めの)書籍に掲載されていたものをほぼそのまま実装してみました。
0. 前提条件
- Debian GNU/Linux 10.3 (64bit) での作業を想定。
- GCC 9.2.0 (G++ 9.2.0) (C++17) でのコンパイルを想定。
- カンマ前後のスペースは除去しない。
1. C++ コードの作成
File: csv.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#ifndef TEST_CSV_CSV_HPP_
#define TEST_CSV_CSV_HPP_
#include <iostream>
#include <string>
#include <vector>
// CSV クラス
class Csv { // カンマで区切られた値を読んで解析する
std::istream& fin; // 入力ファイルポインタ
std::string line; // 入力行
std::vector<std::string> field; // フィールド文字列
unsigned int nfield; // フィールド数
std::string fieldsep; // セパレータ文字
int split();
int endofline(char);
int advplain(const std::string& line, std::string& fld, int);
int advquoted(const std::string& line, std::string& fld, int);
public:
Csv(std::istream& fin = std::cin, std::string sep = ",") :
fin(fin), fieldsep(sep) {}
int getline(std::string&);
std::string getfield(unsigned int n);
int getnfield() const { return nfield; }
};
#endif
File: csv.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include "csv.hpp"
#include <iostream>
#include <string>
#include <vector>
// getline: 1行取得し、必要に応じて伸張
int Csv::getline(std::string& str) {
char c;
for (line = ""; fin.get(c) && !endofline(c); )
line += c;
split();
str = line;
return !fin.eof();
}
// endofline: \r, \n, \r\n, EOF をチェックして捨てる
int Csv::endofline(char c) {
int eol;
eol = (c == '\n' || c == '\n');
if (c == '\r') {
fin.get(c);
if (!fin.eof() && c != '\n')
fin.putback(c); // 読みすぎ
}
return eol;
}
// split: 行をフィールド単位に分割
int Csv::split() {
std::string fld;
unsigned int i, j;
nfield = 0;
if (line.length() == 0)
return 0;
i = 0;
do {
if (i < line.length() && line[i] == '"')
j = advquoted(line, fld, ++i); // クォートをスキップ
else
j = advplain(line, fld, i);
if (nfield >= field.size())
field.push_back(fld);
else
field[nfield] = fld;
nfield++;
i = j + 1;
} while (j < line.length());
return nfield;
}
// advquoted: クォートでくくられたフィールド: 次のセパレータのインデックスを返す
int Csv::advquoted(const std::string& s, std::string& fld, int i) {
unsigned int j;
fld = "";
for (j = i; j < s.length(); j++) {
if (s[j] == '"' && s[++j] != '"') {
unsigned int k = s.find_first_of(fieldsep, j);
if (k > s.length()) // セパレータが見つからなかった
k = s.length();
for (k -= j; k-- > 0; )
fld += s[j++];
break;
}
fld += s[j];
}
return j;
}
// advplain: クォートでくくられていないフィールド: 次のセパレータのインデックスを返す
int Csv::advplain(const std::string& s, std::string& fld, int i) {
unsigned int j;
j = s.find_first_of(fieldsep, i); // セパレータを探す
if (j > s.length()) // 見つからなかった
j = s.length();
fld = std::string(s, i, j - i);
return j;
}
// getfield: n番目のフィールドを返す
std::string Csv::getfield(unsigned int n) {
if (n < 0 || n >= nfield)
return "";
else
return field[n];
}
File: test_csv.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*********************************************
* CSV 読み込みテスト *
*********************************************/
#include "csv.hpp"
#include <iostream>
#include <string>
#include <vector>
// TestCsv main: Csv クラスのテスト
int main(void) {
std::string line;
Csv csv;
while (csv.getline(line) != 0) {
std::cout << "line = '" << line << "'\n";
for (int i = 0; i < csv.getnfield(); i++)
std::cout << "field[" << i << "] = '"
<< csv.getfield(i) << "'\n";
}
}
2. コンパイル
今回は Makefile を作成してコンパイルする。(インデントは、実際は、スペースではなくタブ文字)
File: Makefile
gcc_options = -std=c++17 -Wall -O2 --pedantic-errors
test_csv: test_csv.o csv.o
g++ $(gcc_options) -o $@ $^
test_csv.o : test_csv.cpp
g++ $(gcc_options) -c $<
csv.o : csv.cpp
g++ $(gcc_options) -c $<
run : test_csv
./test_csv
clean :
rm -f ./test_csv
rm -f ./*.o
.PHONY : run clean
コンパイル。
$ make
- 再コンパイルする場合は、
make clean
をしてから。
3. CSV データサンプル
テスト用に CSV データファイルを作成する。(ファイル名: sample.csv
)
File: sample.csv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
20110106,-0.2
20110512,-0.3
20111104,-0.4
20120209,-0.5
20120510,-0.6
20120701, 0.4
20121025, 0.3
20130131, 0.2
20130411, 0.1
20130822, 0.0
20131121,-0.1
20140220,-0.2
20140508,-0.3
20140925,-0.4
20141225,-0.5
20150319,-0.6
20150528,-0.7
20150701, 0.3
20150917, 0.2
20151126, 0.1
20160131, 0.0
20160324,-0.1
20160519,-0.2
20160901,-0.3
4. 実行
ファイルをリダイレクトして、実行。
$ ./test_csv < sample.csv
line = '20110106,-0.2'
field[0] = '20110106'
field[1] = '-0.2'
line = '20110512,-0.3'
field[0] = '20110512'
field[1] = '-0.3'
line = '20111104,-0.4'
field[0] = '20111104'
field[1] = '-0.4'
line = '20120209,-0.5'
field[0] = '20120209'
field[1] = '-0.5'
line = '20120510,-0.6'
field[0] = '20120510'
field[1] = '-0.6'
line = '20120701, 0.4'
field[0] = '20120701'
field[1] = ' 0.4'
line = '20121025, 0.3'
field[0] = '20121025'
field[1] = ' 0.3'
line = '20130131, 0.2'
field[0] = '20130131'
field[1] = ' 0.2'
line = '20130411, 0.1'
field[0] = '20130411'
field[1] = ' 0.1'
line = '20130822, 0.0'
field[0] = '20130822'
field[1] = ' 0.0'
line = '20131121,-0.1'
field[0] = '20131121'
field[1] = '-0.1'
line = '20140220,-0.2'
field[0] = '20140220'
field[1] = '-0.2'
line = '20140508,-0.3'
field[0] = '20140508'
field[1] = '-0.3'
line = '20140925,-0.4'
field[0] = '20140925'
field[1] = '-0.4'
line = '20141225,-0.5'
field[0] = '20141225'
field[1] = '-0.5'
line = '20150319,-0.6'
field[0] = '20150319'
field[1] = '-0.6'
line = '20150528,-0.7'
field[0] = '20150528'
field[1] = '-0.7'
line = '20150701, 0.3'
field[0] = '20150701'
field[1] = ' 0.3'
line = '20150917, 0.2'
field[0] = '20150917'
field[1] = ' 0.2'
line = '20151126, 0.1'
field[0] = '20151126'
field[1] = ' 0.1'
line = '20160131, 0.0'
field[0] = '20160131'
field[1] = ' 0.0'
line = '20160324,-0.1'
field[0] = '20160324'
field[1] = '-0.1'
line = '20160519,-0.2'
field[0] = '20160519'
field[1] = '-0.2'
line = '20160901,-0.3'
field[0] = '20160901'
field[1] = '-0.3'
リダイレクトなしで実行した場合は、1行ずつ、直接入力する。(終了は CTRL-C
)
$ ./test_csv
1,22,333,aaa,bbb
line = '1,22,333,aaa,bbb'
field[0] = '1'
field[1] = '22'
field[2] = '333'
field[3] = 'aaa'
field[4] = 'bbb'
2,"ABC","DEF",9
line = '2,"ABC","DEF",9'
field[0] = '2'
field[1] = 'ABC'
field[2] = 'DEF'
field[3] = '9'
^C
あちこちで利用できるでしょう。
以上。
Comments