stringstream で簡単 split
iPhone 触ってみたいですねー。商標でもめてますが、いっそのこと Apple Phone でも良いような。
さて、C++ で独自フォーマットのテキストファイルを読み込む場合には、find_first_* と substr を組み合わせて泥臭く処理をすることが多かったのですが、『区切り文字を空白に置換する』という前処理を追加することで、stringstream を活用できることに気付きました。いくつか例を挙げて紹介していきます。
まず最初に、以下のようなカンマ区切りのファイルを読み込む例です。
### ### sample.csv ### 1,tom,dvorak,1.0 1,bob,qwerty,0.5 ...
コードは以下の通り。
struct csvdata { int type; std::string key; std::string value; float priority; }; std::ifstream ifs("sample.csv"); std::string line; csvdata data; while(getline(ifs, line)) { if(line.empty() || line[0] == '#') continue; // ',' を空白に置換して std::replace(line.begin(), line.end(), ',', ' '); // 分解 std::stringstream buf(line); // 読み取り if(buf >> data.type >> data.key >> data.value >> data.priority) { ...
型変換も自動的に行なえるのでかなりシンプルです。ただし、このままでは文字列に空白を含めることができません。空白を禁止する仕様とするか、それが許されないのであれば、std::replace する前にエンコードして読み出し後にデコードするなどの対処が必要になります。
次は split 関数の例です。
std::vector<std::string> split(const std::string& str, const std::string& delimiter) { // delimiter(2 文字以上も可) を空白に置換 std::string item(str); for(unsigned pos = item.find(delimiter); pos != std::string::npos; pos = item.find(delimiter, pos)) { item.replace(pos, delimiter.size(), " "); } // 分解 std::stringstream buf(item); // 読み取り std::vector<std::string> result; while(buf >> item) { result.push_back(item); } return result; }
区切り文字で文字列を分割した結果を std::vector に格納して返しますが、空値は無視され、空白で分割されてしまいます。見た目のシンプルさと引き換えに、機能面のトレードオフがあるというわけです。まとめると、
メリット
- シンプルで安全(range_error に怯えなくて良い)
- 型変換サポート
デメリット
- データに空白を含めるなら、エンコード/デコードが必要
- 空値は無視される
になります。
数百行程度のテキストを処理するのであればパフォーマンスも問題にはならないでしょう。
ミニパーサーとしても使える stringstream は、なかなか魅力的な標準ライブラリです。
追記(25:10)
サンプルを若干修正