先日見つけた、tokenize処理のC++コード(元のコードの酷さを壊さない程度に整えてある)
void tokenize(
std::vector< std::string >& result,
const std::string& input,
const char delimiter
)
{
char* buf = new (std::nothrow) char[ input.size() + 1 ] ;
assert( buf != 0 ) ;
buf[0] = '\0' ;
char* buf_ptr = buf ;
for(std::string::const_iterator it = input.begin() ; it != input.end(); ++it ) {
const char &c = *it;
if( delimiter c || '\r' c || '\t' == c ) {
*buf_ptr = '\0' ;
if( buf[0] ) {
result.push_back( buf );
buf_ptr = buf ;
}
}
else {
*buf_ptr++ = *it ;
}
}
*buf_ptr = '\0';
if( buf[0] ) {
result.push_back( buf ) ;
}
delete[] buf ;
}
関数始まった途端ツッコミどころである。
ていうか、それ Boost String Algorithms Library で出来るよ。とか、Boost使わないまでも、もっとスマートに書けるよね。 と思ったのでちょちょっと書いてみた。
- 指定されたdelimiter以外にも'\r'や'\t'もdelimiterになる。
- 連続したdelimiterは1つのdelimiterとして扱う。
- 文字列の先頭・末尾の(連続した)delimiterは無視する。
以上3点に気をつけつつ...
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <cstdlib>
#include <cassert>
typedef std::vector<std::string> container_t;
// Boost.Algorithm
void tokenize_boost(
container_t& result,
const std::string& input,
const char delimiter
)
{
namespace algo = boost::algorithm;
const char separator[] = { delimiter, '\r', '\t', '\0' };
const std::string trimmed_input = algo::trim_copy_if(input, algo::is_any_of(separator));
if ( trimmed_input.length() ) {
algo::split(
result,
trimmed_input,
algo::is_any_of(separator),
algo::token_compress_on
);
}
}
// 標準ライブラリのみ
void tokenize_std(
container_t& result,
const std::string& input,
const char delimiter
)
{
const char separator[] = { delimiter, '\r', '\t', '\0' };
std::string::size_type idx=0;
while (1) {
const std::string::size_type offset = input.find_first_not_of(separator, idx);
if ( offset == std::string::npos )
break;
idx = input.find_first_of(separator, offset);
if ( idx == std::string::npos ) {
result.push_back(input.substr(offset));
break;
}
result.push_back(input.substr(offset, idx-offset));
}
}
// 問題のオリジナル版
void tokenize_original(
container_t& result,
const std::string& input,
const char delimiter
)
{
char* buf = new (std::nothrow) char[ input.size() + 1 ] ;
assert( buf != 0 ) ;
buf[0] = '\0' ;
char* buf_ptr = buf ;
for(std::string::const_iterator it = input.begin() ; it != input.end(); ++it ) {
const char &c = *it;
if( delimiter c || '\r' c || '\t' == c ) {
*buf_ptr = '\0' ;
if( buf[0] ) {
result.push_back( buf ) ;
buf_ptr = buf ;
}
}
else {
*buf_ptr++ = *it ;
}
}
*buf_ptr = '\0';
if( buf[0] ) {
result.push_back( buf ) ;
}
delete[] buf ;
}
// --- ↓↓ 以下、お試しコード
std::string escape(const std::string& org)
{
std::string result(org);
std::string::size_type idx = 0;
while((idx = result.find_first_of("\r\t", idx)) != std::string::npos)
{
result.replace(idx, 1, result[idx] == '\r' ? "\\r" : "\\t");
++idx;
}
return result;
}
void tokenize_test(const std::string& input)
{
std::vector< std::string > strvec;
std::cout << escape(input) << "\n";
tokenize_original(strvec, input, ',');
std::copy(strvec.begin(), strvec.end(),
std::ostream_iterator<std::string>(std::cout, "|"));
std::cout << "/original\n";
strvec.clear();
tokenize_boost(strvec, input, ',');
std::copy(strvec.begin(), strvec.end(),
std::ostream_iterator<std::string>(std::cout, "|"));
std::cout << "/boost\n";
strvec.clear();
tokenize_std(strvec, input, ',');
std::copy(strvec.begin(), strvec.end(),
std::ostream_iterator<std::string>(std::cout, "|"));
std::cout << "/std\n\n";
}
int main() {
std::vector< std::string > strvec;
tokenize_test("\tabc,def\r\tghi\tjkl\t\r\r");
tokenize_test("abc,def\r,,,\tghi\t\r\rjkl");
tokenize_test("abcdefg");
tokenize_test(",,,,,,,,,");
return EXIT_SUCCESS;
}
後、tokenizeとは関係ないけど、同じプロジェクトの別の場所で定義されていた配列風クラスのコメントに 「std::vectorの模倣だけど(std::vectorとは違って)連続したメモリ領域を使うよ」(意訳) って書いてあった。
誰が書いたんか知らんけど、 ISO/IEC 14882:2003 の §23.2.4-1 を熟読することをおすすめしたい。





コメントする