本日は軽いネタを紹介しようと思います.
周期的な数パターンをループさせたい時ってありませんか?
具体的には [0,1,2,3,0,1,2,3,0,1...], [3,2,1,0,3,2,1,0,3,2...] というような形で変数を周期的に変化させたいケースです.
最近,私はキャラクターを上下左右に時計回りと反時計回りで回転させたかった時にこの実装と出会いました.
この時の実装方法でちょっと綺麗な実装を思いついたのでご紹介します.
周期的なインクリメント
キャラクターを上下左右に回転させる場合について考えていきます.
上=0, 右=1, 下=2, 左=3と数を割り振ると,キャラクターの時計回りの回転操作は,周期的なインクリメントと言えます.
この場合ループする変数はキャラクターの方向となります.
ループの際のパターン数は上下左右なので4通りとなります.
ここで,周期的なインクリメントは,ループ対象の変数をインクリメントした後,パターン数で割って余りを計算するのがおすすめです.
これはほとんどの方がすぐに想像できると思います.
以下にキャラクターを上下左右に時計回りで回転させる場合のGoの実装例を示します.
rotate_clockwise()
メソッドにご注目ください.
const (
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3
)
type character struct {
direction int
}
func (c *character) rotate_clockwise() { // 時計回り
c.direction = (c.direction + 1) % 4
}
func main() {
c := character{direction: UP} // 初期方向をUPに設定
fmt.Printf("Initial direction: %d\n", c.direction)
for i := 0; i < 8; i++ {
c.rotate_clockwise()
fmt.Printf("Direction after rotate %d: %d\n", i+1, c.direction)
}
}
Initial direction: 0
Direction after rotate 1: 1
Direction after rotate 2: 2
Direction after rotate 3: 3
Direction after rotate 4: 0
Direction after rotate 5: 1
Direction after rotate 6: 2
Direction after rotate 7: 3
Direction after rotate 8: 0
周期的なデクリメント
キャラクターを上下左右に回転させる場合について考えていきます.
上=0, 右=1, 下=2, 左=3と数を割り振ると,キャラクターの反時計回りの回転操作は,周期的なデクリメントと言えます.
先ほどと同じく,ループする変数はキャラクターの方向,ループの際のパターン数は上下左右なので4通りとなります.
ここで周期的なデクリメントは,ループ対象の変数にパターン数を足してデクリメントした後,パターン数で割って余りを計算するのがおすすめです.
これはやや直感的でないので思いつかない人も多いかもしれません.
注意点として,言語によってはパターン数を足さないと動作しない場合があります.
パターン数を足さない実装をする人は,c.direction = (c.direction - 1) % 4
のように書きます.
これは,-1 % 4 = 3, なぜならば -1 = 4 * (-1) + 3
という考え方です.
これは少なくともGoでは動作しません.(-1 % 4 = -1
となります.)
このようにプログラミング言語によっては余りの計算方法が数学的な考えと異なる場合もあるので,今回紹介する実装のほうが好ましいでしょう.
以下にキャラクターを上下左右に反時計回りで回転させる場合のGoの実装例を示します.
rotate_counterclockwise()
メソッドにご注目ください.
const (
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3
)
type character struct {
direction int
}
func (c *character) rotate_counterclockwise() { // 反時計回り
c.direction = (c.direction + 4 - 1) % 4
}
func main() {
c := character{direction: UP} // 初期方向をUPに設定
fmt.Printf("Initial direction: %d\n", c.direction)
for i := 0; i < 8; i++ {
c.rotate_counterclockwise()
fmt.Printf("Direction after rotate %d: %d\n", i+1, c.direction)
}
}
Initial direction: 0
Direction after rotate 1: 3
Direction after rotate 2: 2
Direction after rotate 3: 1
Direction after rotate 4: 0
Direction after rotate 5: 3
Direction after rotate 6: 2
Direction after rotate 7: 1
Direction after rotate 8: 0
rotate_counterclockwise()メソッドの実装は,剰余演算子%を使うことで,以下のような実装よりもすっきりしています.
func (c *character) rotate_counterclockwise() { // 反時計回り
c.direction--
if c.direction < 0 {
c.direction = 3
}
}
いかがでしたでしょうか?
今後このパターンは,綺麗に書けるようにしたいですね.