普通のBoostプログラミング

この記事はC++ Advent Calendar jp 2010 : ATNDの7日目の記事です.
他の記事と比べちゃダメです.
わたしはほんわか担当なので.


テーマは(自分の力量制限により)「普通のBoostプログラミング」です.
最近Boostを使ったコード片の比較的つまらなくなさそうな部分をまとめてみました.


やりたいことは以下の事です.

  • このシステムには周期があり,毎周期毎に変化するデータXが入力される
  • それとは別に固定のデータ列Yがある
  • 新しい方から一定周期F毎のデータXと,先頭から順のデータYを最大N個同時に取得したい


図0.大変分かりにくい状況画像


データXとデータYはセットにして使うので同時に取得しています.
また,データYは固定なのになぜ別の場所から取得しないのかというと,(実は)密接に関連しているからです.
状況的に良く分からないかもしれませんが,そういうものだと思って下さい.


まずデータXの保存を考えます.
データXは最新F*N個(くらい)を覚えていれば良く,それより古いデータは破棄して構いません.
そこでBoost.CircularBufferが使ってみたかった良さげなのでこれに格納します.

データYはN個です.
データXに依存するか否かに関係なく個数は固定なので,std::arrayで問題無いです.

データの取得はBoost.RangeのIteratorRangeで行います.
データXは(F*n){nは0〜N-1}番目を取得したいので,それ以外をスキップするイテレータを使います.
インクリメントする毎にF進むイテレータを作った方が良いかもしれませんが,今回は既にあるboost::permutation_iteratorを使います.
最後に,データXのイテレータとデータYのイテレータを同時に返す必要があるため,正にそのためにあるboost::zip_iteratorでまとめます.


まずはtypedefなどをしちゃいます.
#元がクラスなのでちょっと名前とかあれですが.

#include <array>
#include <cstddef>
#include <boost/circular_buffer.hpp>
#include <boost/iterator/permutation_iterator.hpp>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/tuple/tuple.hpp>

namespace {

  const std::size_t F = 12;
  const std::size_t N = 5;

}

struct X{};
struct Y{};
typedef boost::circular_buffer<X> X_container;
typedef std::array<std::size_t, N> X_indices_container;
typedef std::array<Y, N> Y_container;
typedef boost::zip_iterator<
  boost::tuple<
    boost::permutation_iterator<
      X_container::const_iterator,
      X_indices_container::const_iterator
    >,
    Y_container::const_iterator
  >
> const_iterator;

X_container,Y_containerはそれぞれデータX,Yを格納する型です.
boost::permutation_iteratorは2番目のイテレータで指定した順番に1番目のイテレータにアクセスするイテレータです.
なので何番目にアクセスするか,をX_indices_containerに格納してそれをpermutation_iteratorに渡しています.
最後に,Xを取得するイテレータであるpermutation_iteratorとY_container::const_iteratorをtupleにまとめてboost::zip_iteratorに与えます.
std::tupleでなくboost::tupleなのは,std::tupleがboost::zip_iteratorの要件を満たしていないからです.


次に変数宣言と初期化をします.

#include <functional>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm_ext/overwrite.hpp>
#include <boost/range/counting_range.hpp>

X_container Xs;
Y_container Ys;
X_indices_container X_indices;

using boost::adaptors::transformed;
using std::placeholders::_1;

boost::overwrite(boost::counting_range(0U, N)|transformed(std::bind(std::multiplies<std::size_t>(), _1, F)), X_indices);

Ys = get_Y_values();


Xsは周期毎にデータが追加されるので空で良いです.
Ysには適当な値を入れます.
X_indicesにはXs内の取得する値のインデックスを入れます(つまり,F*0, F*1, F*2, ...F*(N-1)です).


次に毎周期毎にデータXを追加する関数です.

void push(const X& x){

  Xs.push_back(x);

}

はい.


次に値を取得する関数です.
ラスボスです.

#include <iterator>
#include <boost/assert.hpp>
#include <boost/range/iterator_range.hpp>

boost::iterator_range<const_iterator> get(){

  const std::size_t valid_size = static_cast<std::size_t>((Xs.size() + F - 1) / F);
  BOOST_ASSERT(valid_size <= N);

  const auto xs_begin = boost::make_permutation_iterator(Xs.begin(), X_indices.begin());
  auto xs_end = xs_begin;
  std::advance(xs_end, valid_size);

  const auto ys_begin = Ys.begin();
  auto ys_end = ys_begin;
  std::advance(ys_end, valid_size);

  return boost::make_iterator_range(
    boost::make_zip_iterator(boost::make_tuple(xs_begin, ys_begin)),
    boost::make_zip_iterator(boost::make_tuple(xs_end, ys_end))
  );

}

最初はデータがF*N個ないので,始めに取得するデータの個数を計算しています(Xs.size() + F - 1をNで割って切り捨てです).
次にXsとYsのイテレータを作成しています.
endはbeginから取得するデータの個数分だけ進めています.
最後にzip_iteratorを作りそれをiterator_rangeとして返しています.


ちなみに,データの取得は次のようにできます.

#include <boost/foreach.hpp>

BOOST_FOREACH(const auto& elem, get()){

  const X& x = elem.get<0>();
  const Y& y = elem.get<1>();

}


あとがき.

はい,普通にBoost.CircularBuffer,Boost.Iterators,Boost.Rangeを使ってみました.
Boost.Iteratorsの使い道が良く分かってなかったのですが,クラス内で保存しているデータを外部へ公開する時にちょっと変換したい場合などに便利ですね.むしろそれ用ですかね.


最後に.
信じられないかもしれませんが,この記事はC++ Advent Calendar jp 2010 : ATNDの7日目の記事です.


壁|oノωノ) )))))))・・・イヤーン♪