インデックスループ
テーマ
ここに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つの計算式が同じ性質であることを強調している。ただし、一見何をやっているのか理解されない可能性もあるため、慣れない相手にはフォローが必要になるかもしれない。