PrevRef: 前回値参照ループをサポートする汎用ファンクタ

今回は前回に考えたPrevsMustGoクラスの抽象度を上げたクラスを考えてみるぞ。PrevsMustGoは、前回値を参照するループ処理を、for_eachで行えるようにするための試みなんだったね。今日発表するPrevRef ファンクタ(ソースは以下)はその汎用性を高めたものだよ。

// PrevRef 
//(VC++2008SP1のみにて確認)
template< class PrevType, class Func >
struct Prev_t: public unary_function
< typename Func::result_type, const PrevType& >{
  Prev_t( const PrevType& ini, Func& func )
   :_prev( ini ), _func( func ){}
  typename Func::result_type 
    operator()( const PrevType& cur ) const 
  { 
    typename Func::result_type result = _func( _prev, cur ); 
    _prev = cur;
    return result;
  }
  mutable PrevType _prev;
  Func _func;
};
template< class PrevType, class Func >
Prev_t< PrevType, Func > 
  PrevRef( const PrevType& ini, Func& func )
{ 
  return Prev_t< PrevType, Func >( ini, func ); 
}

PrevRefファンクタは、前回値を自らの中に格納し、呼び出されるたびに指定のFuncを呼び出し、それが終わったら前回値を今回値で更新するというだけのものだ。PrevRefのコンストラクタは、前回値の初期値と、ループ処理の中身としてのFuncを受け取るようになっている。このFuncに要求するポリシーとしては(前回値、今回値)の2引数呼び出しができ、void以外の値を返すこと、それだけだ。
早速使い方を見ていこう。前回PrevsMestGoを定義して行っていたこんな計算

struct PrevsMustGo
{
  PrevsMustGo( void ){ _prev=0; }
  int operator()( const int a ) const { 
    int tmp= a-_prev;
    _prev = a;
    return tmp;
  }
  mutable int _prev;
};
transform( il.begin(), il.end(), back_inserter( idv ), PrevsMustGo() );

は、PrevRefを使ってこう書けるようになった。↓

transform( il.begin(), il.end(), back_inserter( idv ), 
  PrevRef( 0, std::minus<int>() ) );

あるいは結果の符号を反転したければ、以前紹介したNestCallとstd::negateを使って、以下のように書ける。

transform( il.begin(), il.end(), back_inserter( idv ), 
  NestCall( PrevRef( 0, std::minus<int>() ), negate<int>() ));

PrevRefがその本当の威力を発揮するのは、このNestCallと併用した場合かも知れない。例えば数値列の1階差分のみでなく、2階、3階差分を求めたくなった場合、

// il の、1階差分 idv に求める
transform( il.begin(), il.end(), back_inserter( idv ), 
  PrevRef( 0, std::minus<int>() ) );        
// il の、2階差分をiddvに求める
transform( il.begin(), il.end(), back_inserter( iddv ), 
  NestCall( PrevRef( 0, std::minus<int>() ), PrevRef( 0, std::minus<int>() ) ) );        
// il の、3階差分をiddvに追記
transform( il.begin(), il.end(), back_inserter( iddv ), 
  NestCall( PrevRef( 0, std::minus<int>() ), 
  NestCall( PrevRef( 0, std::minus<int>() ), PrevRef( 0, std::minus<int>() ) ) ) );

見にくいのでファンクタ生成部のみをマクロ化すると、、

//#define GRADIENT PrevRef( 0, std::minus<int>() )  
#define GRADIENT NestCall( PrevRef( 0, std::minus<int>() ), negate<int>() )

// il の、1階差分 idv に求める
transform( il.begin(), il.end(), back_inserter( idv ), 
  GRADIENT );        
// il の、2階差分をiddvに求める
transform( il.begin(), il.end(), back_inserter( iddv ), 
  NestCall( GRADIENT, GRADIENT ) );        
// il の、3階差分をiddvに追記
transform( il.begin(), il.end(), back_inserter( iddv ), 
  NestCall( GRADIENT, NestCall( GRADIENT, GRADIENT ) ) );

#undef GRADIENT

とこのように、NestCallとの併用で、任意整数数列の、3階差分(要するに任意N階差分)まで計算するコードが、何と一行で書けてしまうのであった。3階差分まで計算するプログラムを普通のfor 分でガリガリ書いたときの状況との差を想像してみてほしい。(←しかし実はそっちの方がコード量は少なくできるかも知れないが、、)
そうすると、1,2,3階差分のそれぞれについて、「ループを3階回さなくてはならないのかー、やっぱ普通のfor分で書いて、ilに関するループは1回のみにおさえたいな」と思ったりもするかも知れないのだが、その場合は以下のような工夫をすればよい。
std::transform を使うのではなく、結果をファンクタに指定したコンテナに格納してくれる方が有難い場合もある。ここでは上記の GRADIENT にあたるファンクタクラスの特別バージョンを用意する。本クラスが特別なのは、計算結果を格納するためのコンテナを、それへのポインタを渡すという形で指定可能な点だ。

struct Gradient: public binary_function< const int, const int, int >
{
  Gradient( vector< int >* pil ): _pil(pil){}
  int operator()( const int prev, const int cur ) const
  {
    _pil->push_back( cur-prev ); 
    return _pil->back();
  }
  mutable vector< int >* _pil;
};
  
#define GRAD2(x) PrevRef( 0,Gradient( x ) )

// il の、1階差分 idv に求める
for_each( il.begin(), il.end(), GRAD2( &idv ) );
// il の、1階差分 idv に求め、2階差分を、iddvに求めます
for_each( il.begin(), il.end(), NestCall( GRAD2( &idv ), GRAD2( &iddv ) ) );
// il の、1階差分 idv に求め、2階差分を、iddvに求め、3階差分を、idddvに求めます
for_each( il.begin(), il.end(), 
    NestCall( GRAD2( &idv ), 
      NestCall( GRAD2( &iddv ), GRAD2( &idddv ) ) ) );

こうすれば、il に関してループは1回だけまわる中で、その1、2,3階差分数列が、それぞれ、idv, iddv, idddv に求まる。
最後に結果確認だ。ここでは、上記の一番最後のfor_each分の行のみを実行した結果を示そう。

// 結果を確認
for each( int i in il ){   cout<< i<< ", ";   } cout<<endl;
for each( int i in idv ){   cout<< i<< ", ";   } cout<<endl;
for each( int i in iddv ){   cout<< i<< ", ";   } cout<<endl;
for each( int i in idddv ){   cout<< i<< ", ";   } cout<<endl;

//     0, 1, 4, 9, 16, 25, 36, 49, 64, 81, ←二乗数列
//    0, 1, 3, 5, 7, 9, 11, 13, 15, 17, ←1階差分
//   0, 1, 2, 2, 2, 2, 2, 2, 2, 2,←2階差分
//  0, 1, 1, 0, 0, 0, 0, 0, 0, 0,←3階差分