以下のようなmy_key_valというクラスがあるとします.
struct my_key_val { my_key_val() : key(int()), val(double()) { } my_key_val(int key, double val = double()) : key(key), val(val) { } int key; double val; };
これをkeyについてソートしてvector
typedef std::vectorkey_val_vec; key_val_vec v;
もちろん,my_key_valに対してkeyによる順序を定義する関数オブジェクトが定義されている必要があるでしょう.
struct key_less : public std::binary_function< my_key_val, my_key_val, bool> { bool operator()( my_key_val const &lhs, my_key_val const &rhs) const { return std::less()(lhs.key, rhs.key); } };
一方で,std::map
std::mapm;
ここで,双方のkey(int)が共通する要素を取り出したいとします.
明示的にループを回すのが簡単ですが,STLを使っている以上algorithmを使いたいものです.『Effective STL』の第43項にも,明示的なloopよりもalgorithmを優先して使うべきであるという,非常に説得力のある,そして魅力的な理由が提示されています.
さて,上の例の場合,該当するアルゴリズムはset_intersectionあたりでしょう.しかし,本当にset_intersectionを使えるのでしょうか?『異なるコンテナ同士』の共通要素を引っ張ってくることは問題ではありません.そのためにiteratorが用意されているのですから.問題は,その要素の型がそもそも違うことにあります.
以下のコードはそのままではコンパイルされません.
key_val_vec tmp; std::set_intersection( m.begin(), m.end(), // 範囲その1 v.begin(), v.end(), // 範囲その2 std::back_inserter(tmp), // 共通要素の代入先 m.key_comp()); // 順序を定義する述語(以下predとします)
そもそも,set_intersectionは本来std::set用なので,こういう使い方は本来推奨されないのかも知れません.ただ,気をつけて使えば,非常に有用であると私は主張します.
set_intersectionは指定した述語を使って範囲その1の要素と範囲その2の要素を比較し,共通した要素が存在するなら,範囲その1から代入先へ要素をコピーするアルゴリズムです.
しかし,上で指定している述語はmapの要素pairのfirstメンバに対する比較を行う述語なので,mの要素のkeyとvの要素のkeyとの比較に用いることが出来ません.また,そのままではmの要素をtmpにコピーすることも出来ません.
以上の2点から,このコードはコンパイルされません.
述語は,pred(mの要素, vの要素)とpred(vの要素, mの要素)の二つの形で用いられるため,predのメンバ関数operator()に2つの形式をoverloadし,vの範囲をその1に,mの範囲をその2に持ってくれば一応,コンパイルは通る形になります.
これはこれで一つのsolutionだと思うのですが,私としてはboost::transform_iteratorを用いるのが非常に有用で,かつ他のアルゴリズムの使用に際しても広く応用できるのではないかと考えました.
std::set_intersection( boost::make_transform_iterator(m.begin(), pair_to_my_key_val()), boost::make_transform_iterator(m.end(), pair_to_my_key_val()), v.begin(), v.end(), std::back_inserter(tmp), key_less());
ここで,pair_to_my_key_valはmの要素の型であるpair
struct pair_to_my_key_val : public std::unary_function< std::map::value_type, my_key_val> { my_key_val operator()(std::map ::const_reference r) const { return my_key_val(r.first, r.second); } };
これによって,何の問題もなく目的を達成できます.また,key_val_vecのソートに用いていたkey_lessファンクタをそのまま流用できています.
さらに,
std::set_intersection( v.begin(), v.end(), boost::make_transform_iterator(m.begin(), pair_to_my_key_val()), boost::make_transform_iterator(m.end(), pair_to_my_key_val()), std::back_inserter(tmp), key_less());
と範囲1と範囲2を交換して使っても何の問題もありません(注:先の使い方とは意味が違ってきます).
このようなtransform_iteratorとSTLアルゴリズム群の組み合わせには,無限の可能性を私は感じるのですが,皆さんはどうでしょうか?
(上のようなtransform_iteratorの使い方で大きく問題となるのは,変換がコンパイラでどこまで最適化されるか,だと思われます.これの議論についてはまた,今度時間があればやりたいと思います.少なくとも,上のコードではRVOはかかるはずです.それ以上の,より積極的な最適化(比較のためだけに生成される一時オブジェクトが削除されるかなど)については大きく疑問があるでしょう)
#追記:効率の問題に対してはprojection_iteratorを使う手がありますね.
さらに付け加えて言うと,もしtransform_iteratorに引き渡すfunctorをlambdaでinlineに書けたとしたら?(実際には,これにはいくつか障害があります)
上の問題を考えるうちに,そんなSTLアルゴリズム + transform_iterator + Lambdaという夢のような競演を思いついたので,次回(というか今からすぐに書きますがw)提案してみたいと思います.
補足1:上の例は非常に簡単な例で,本来ならvectorの方をvector
補足2:set_intersectionに関する標準の文言があまり明瞭なものではないので,その点でだいぶ議論の余地があるように思われます.一番確実であろう方法は,双方の要素からkey値だけに変換するファンクタによるtransform_iteratorを書いて,共通するkey値の集合を得ることだと思われます.(ただこれだけではあんまり意味がないかと)
最後に,今日の問題に対する簡単な例示ソースコードをここにおいておきます.