C++
インデックス
剰余
インデックスループ
小さな技術を紹介するシリーズ

C++ の小さな技術を紹介するシリーズ【小技C++ 全9回】#1<インデックスループ>

インデックスループ

テーマ

ここに0から3までの数字を振った4つの箱と、箱を指定するためのカーソルが1つある。
カーソルは値を持ち、その値と同じ番号の箱を指定する。

[0]  [1]  [2]  [3]
 ^
 |
 0

カーソルが右へ移動するとき、0から3までの箱を順番に指定したのち、再び0へ戻りループするためには、カーソルの値をどのように計算すればよいだろうか。また、カーソルが左へ移動するときはどのように計算すればよいだろうか。

メソッド

(このシリーズで記述するコードは、あくまで筆者のオススメする案です。よりよい方法や、場面にあった手法を見つけるヒントとして使ってください。そして、楽しんでください!)

// 移動方向の定義

enum class OPERATION
{
    LEFT,
    RIGHT,
};
// op            : 移動方向
// index         : カーソルの値
// LOOP_WIDTH    : ループ幅

void UpdateIndex( const OPERATION op, size_t& index, const size_t LOOP_WIDTH );

if 文で境界をチェックする

void UpdateIndex( const OPERATION op, size_t& index, const size_t LOOP_WIDTH )
{
    if( op == OPERATION::LEFT ) {

        if( index > 0 ) {

            index--;            
        }
        else {

            index = LOOP_WIDTH - 1;
        }
    }
    else if ( op == OPERATION::RIGHT ) {

        if( index < LOOP_WIDTH - 1 ) {

            index++;
        }
        else {

            index = 0;
        }
    }
    else {
    }
}

C++ 初経験者が最初に思いつきやすい(プロの書くコードでもたまに見かける)コードだろう。条件式が多用され、自然言語をそのまま書き起こしたような構造になっているため、説明しやすい。しかし、実現したい計算のわりに、そのコード長が無視できないほどの長さであることは、無駄な保守コストやバグの原因となりやすい傾向にある。

剰余を使って計算する

void UpdateIndex( const OPERATION op, size_t& index, const size_t LOOP_WIDTH )
{
    switch( op )
    {
    case OPERATION::LEFT:
        index = ( index + LOOP_WIDTH - 1 ) % LOOP_WIDTH;
        break;
    case OPERATION::RIGHT:
        index = ( index + LOOP_WIDTH + 1 ) % LOOP_WIDTH;
        break;
    default:
        break;
    }
}

こちらのコードは、条件式による境界チェックを一切行わない。インクリメントされる値の剰余を求めることで、ループする数列を表現する。注意したいのは、剰余を求める前にループ幅と同じだけの「ゲタ」を履かせるところだ。左へ移動する場合に index が0のとき、1減算しても問題のないようにしてくれている。右へ移動する場合に 「ゲタ」は本来必要ないのだが、字面を近くすることで、2つの計算式が同じ性質であることを強調している。ただし、一見何をやっているのか理解されない可能性もあるため、慣れない相手にはフォローが必要になるかもしれない。