巡回インデックスアダプターを発明したよ


やあ子どもたち。ウィンタースポーツやってるか。おじさんの友達がこの前スキーに行ったら腰を痛めてってそれはいいのだけど、とにかく20代の若者がいなかった、リフトなんかも客が少なくて動いてないのとかあったしなんて行ってたけど、寒い時こそ外に出てちゃんと体動かして遊ばないとだめだぞ。スキー場なくなっちまったら大変だからな。
さて今日は巡回インデックスアダプターの紹介だよ。
よく、大きさnの配列 array[i](i=0,1,2,, n-1)なんかが定義されていて、これを巡回配列として扱いたいときがあるね。そんなとき、

「ああ、もしも、
 array[n]→array[0]のことになってくれたら、とか
 array[n+1]→array[1]のことになってくれたら、、、
 あるいは
 array[-1]→array[n-1]のことになってくれたら、とか
 array[-2]→array[n-2]のことになってくれたら、、
 そしたらループがとても綺麗に書けるのに。。」

なんて、夢想したりしたことはないかい。
そんな時こそ今回の巡回インデックスアダプターの出番というわけだ。
ソースをみてみよう。

class IdCirculator
{
public:
    IdCirculator( const int n_size ):_n_size(n_size){}
    int operator()( const int i ){
        return (i<0)? ((i%_n_size)+_n_size)%_n_size : i%_n_size;
    }
    int _n_size;
};

はい。これはなんだろうね。以下、これの使い方だ。

 // ●巡回インデックスアダプターインスタンスの初期化
    IdCirculator ic( 5 ); 
 // 5は巡回させたい配列IDのUpperBound値(配列の大きさとも言う)

 // ●巡回インデックスアダプターインスタンスの利用
   std::cout << ic(-1) <<", "; // 利用箇所
   std::cout << ic(0) <<", "; // 利用箇所
   std::cout << ic(1) <<", "; // 利用箇所
   std::cout << ic(2) <<", "; // 利用箇所
   std::cout << ic(3) <<", "; // 利用箇所
   std::cout << ic(4) <<", "; // 利用箇所
   std::cout << ic(5) <<", "; // 利用箇所
 std::cout<< std::endl;

    for( int i=-5; i<11; ++i )
    {
        std::cout << ic(i) <<", "; // 利用箇所
    }
    std::cout << std::endl;

これの出力は以下のようになるよ。

4, 0, 1, 2, 3, 4, 0,
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 

わかるかな、上記では、idcを5で初期化してるから、idc()には、0から4の値を入れればそのままの値が帰ってくるが、それをはみ出して、-1なんかを指定した場合は巡回を考慮して、4を返してくれるし、−2を指定すれば3が帰ってくる。同じようにして、idc(5)とやれば、0が帰るし、idc(6)は1を返してくるという具合だ。どうだい、えらく便利じゃないかこれ。
なので、冒頭の話でいくと、array[ idc(n) ]と書けば、それはarray[0] のことになるし、array[ idc(-1) ] と書けば、それは、array[ n-1 ] のことになる、というわけで、巡回配列関連のループがとても簡潔に書けるようになるよ。
ただし、条件判定が余計に入るので、パフォーマンス的には、ダウンするよ。でも手っ取り早く、バグを入れずに書いてしまいたい場合なんかは重宝するはずさ。
じゃ今日はそんなところだ。チャオ!

(後日記)(2016年1月19日(火曜日))
でー、時は流れ、ここはこういうラムダ式でいいんじゃないかという説も浮上したのでメモだ。

 auto ic = [](const int i, const int _n_size) 
           {
	     return (i < 0) ? ((i%_n_size) + _n_size) % _n_size : i%_n_size;
	   };

まー引数は増えたけども、こうすればわざわざクラス定義しなくとも、利用したいコードのスコープ内でコードは完結するね。