C++: 文字列を結合し、区切って数値へ変換する
動機
次のような数字と空白で構成された文字列(string)の vector がある。
{ "10", "2 3", "456", " 789 ", "0" }
これらを先頭から順に結合し、空白の位置で再度分割し、数値(int)の vector へ変換したい。
1. 文字列の結合
Scalaの心地よさに触発されたので、foldLeft関数を実装してみた。
自作のfoldLeftに対してSTL標準のplusファンクタ(Scalaで言う_+_)を適用することで結合を実現する。
template <typename Iterator, typename Tp, typename Func> Tp foldLeft(Iterator first, Iterator last, Tp seed, Func f) { for (Iterator it = first; it != last; ++it) seed = f(seed, *it); return seed; } template <typename Container> typename Container::value_type join(Container const& container) { typedef typename Container::value_type Tp; return foldLeft(container.begin(), container.end(), Tp(), std::plus<Tp>()); }
join関数の中で使っている Container::value_type は、typename キーワードを付けないとコンパイルが通らない。
テンプレート変数が型なのか値なのか、今回のようなケースではコンパイラは推論することができないためだ。
2. 文字列の分割
下記URL参照。stringstream と getline を利用した実装。
std::vector<std::string> split(std::string const& str, char delimiter = ' ') { std::istringstream iss(str); std::string buf; std::vector<std::string> result; while (std::getline(iss, buf, delimiter)) result.push_back(buf); return result; }
3. 文字列から数値へ変換
こちらも Functional Programming ライクな map 関数を作成した。
std::map との名前の競合を避けるため fmap と命名したのは不本意であるが詮方無しか。
現状では std::vector から std::vector への変換しかできないが、他のコンテナへも適用可能にするなど
改善の余地はありそうだ。
テンプレート変数の Func は、argument_type および result_type という型名の定義を必要とする仕様とした。
通常は std::unary_function を継承すればよい。
template <typename Func> std::vector<typename Func::result_type> fmap( std::vector<typename Func::argument_type> const& src) { std::vector<typename Func::result_type> result(src.size()); std::transform(src.begin(), src.end(), result.begin(), Func()); return result; }; template <typename Arg, typename Result> class toNumeric : public std::unary_function<Arg, Result> { public: Result operator()(Arg const& str) const { Result result; std::istringstream iss(str); iss >> result; return result; } }; typedef toNumeric<std::string, int> toInt; typedef toNumeric<std::string, long long int> toLong;
個々の文字列から数値への変換は、toNumeric という独自のファンクタを作成した。
この処理でも stringstream を活用している。
toInt が int 型への変換、toLong が long long int 型への変換処理である。
コード全体
#include <iostream> #include <sstream> #include <vector> #include <algorithm> #include <functional> // join template <typename Iterator, typename Tp, typename Func> Tp foldLeft(Iterator first, Iterator last, Tp seed, Func f) { for (Iterator it = first; it != last; ++it) seed = f(seed, *it); return seed; } template <typename Container> typename Container::value_type join(Container const& container) { typedef typename Container::value_type Tp; return foldLeft(container.begin(), container.end(), Tp(), std::plus<Tp>()); } // split std::vector<std::string> split(std::string const& str, char delimiter = ' ') { std::istringstream iss(str); std::string buf; std::vector<std::string> result; while (std::getline(iss, buf, delimiter)) result.push_back(buf); return result; } // map template <typename Func> std::vector<typename Func::result_type> fmap( std::vector<typename Func::argument_type> const& src) { std::vector<typename Func::result_type> result(src.size()); std::transform(src.begin(), src.end(), result.begin(), Func()); return result; }; template <typename Arg, typename Result> class toNumeric : public std::unary_function<Arg, Result> { public: Result operator()(Arg const& str) const { Result result; std::istringstream iss(str); iss >> result; return result; } }; typedef toNumeric<std::string, int> toInt; typedef toNumeric<std::string, long long int> toLong; #define each(i,c) for (typeof((c).begin()) i=(c).begin(); i!=(c).end(); ++i) using namespace std; int main(int argc, char **argv) { string s[] = { "10", "2 3", "456", " 789 ", "0" }; vector<string> vs(s, s + sizeof(s) / sizeof(s[0])); vector<int> vi = fmap<toInt>(split(join(vs))); each(i,vi) cout << *i << endl; }
実行例
102 3456 789 0
References
Story of Your Life » Blog Archive » C++で文字列のsplit:
http://shnya.jp/blog/?p=195
0 件のコメント:
コメントを投稿