まずは、basic_string::substrなんて使わないよねという話です。
“abc def ghi”のような文字列を空白で区切って{“abc”, “def”, “ghi”}のようにしたいとします。普段、私はこんなことにsubstrを使わないのですが、がんばって書いてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <string> #include <deque> std::deque<std::string> Parse(std::string const& src) { auto left = src.find(' '); if (left == std::string::npos) { return {src}; } else { auto right = src.find_first_not_of(' ', left); auto ret = Parse(src.substr(right)); ret.push_front(src.substr(0, left)); return ret; } } |
なぜ、substrを使わないかと言えば、2つの理由があります。
- 文字列のコピーが作られる。
- 切り出し位置の指定を数値で行う(イテレータで指定できない)。
上の例は、再帰呼出のたびにも実のコピーが作られるので、さすがにありえないです。実は、最初に次に示すRangeを使ったものを作り、次にそれをsubstrを使った上のコードへ移植したからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <string> #include <deque> #include <boost/range.hpp> #include <boost/range/algorithm.hpp> std::deque<std::string> Parse(boost::iterator_range<std::string::const_iterator> const& src) { using namespace boost; auto left = find(src, ' '); if (left == end(src)) { return {copy_range<std::string>(src)}; } else { auto right = std::find_if(left, end(src), [](char c) {return c != ' ';}); auto ret = Parse(make_iterator_range(right, end(src))); ret.emplace_front(begin(src), left); // emplace_frontはpush_front(std::string(begin(src), left))と同じ意味 return ret; } } |
最初のコードでは、再帰呼出のたびにstringオブジェクトが作られていましたが、iterator_rangeではそのようなことはありません。iterator_rangeは[先頭, 末尾)のイテレータのペアを保持しているという理解で構いません。
2つのコードの違いの肝は次のあたりです。
「だからsubstrなんてなくても平気」という話ではありません、今日は。そんな文字列解析を手で書くなんて面倒なので、相応のものを使いましょう。正規表現?Boost.StringAlgo?いえいえ、Boost.Spiritです。
1 2 3 4 5 6 7 8 9 10 11 12 | #include <string> #include <deque> #include <boost/spirit/include/qi.hpp> std::deque<std::string> Parse(std::string const& src) { using namespace boost::spirit::qi; std::deque<std::string> ret; parse(src.begin(), src.end(), *(char_ - ' ') % +lit(' '), ret); return ret; } |
せっかくなので、速度を測ってみることにしました。以下のコードは上3つに対し共通で使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <iostream> #include <boost/progress.hpp> #include <boost/for_each.hpp> std::deque<std::string> ParseWithTime(std::string const& src) { boost::progress_timer t(std::clog); return Parse(src); } int main() { std::string s; std::getline(std::cin, s); auto x = ParseWithTime(s); BOOST_FOREACH(auto const& t, x) // for (auto const& t : x) { std::cout << t << std::endl; } } |
こんな結果でした。左端の列は入力に与えたテキストの量です(英語版Wikipediaなどから適当に切り貼りして作りました)。NAと書いた部分はCore dump吐いたので計測できませんでした。
[KiB] | substr | iterator_range | Spirit |
---|---|---|---|
64 | 0.16 | 0.03 | 0.01 |
96 | 1.01 | 0.03 | 0.02 |
128 | 1.81 | 0.05 | 0.02 |
256 | NA | 0.09 | 0.03 |
512 | NA | 0.19 | 0.06 |
1024 | NA | 0.37 | 0.11 |
最初のコードがいかにだめか、あらためてよく分かりますね。
スポンサード リンク |
この記事のカテゴリ
- C++ ⇒ Boost.勉強会#5行ってきたのでSpirit使うのです