Rubyの範囲演算子は降順のイテレートには使えない
かなりハマったのでメモです。
範囲演算子
Rubyには..
と...
の2種類の範囲演算子があります。実態はRangeクラスで、Range#eachメソッドでfor文のように使うイディオムがあります。
irb(main):005:0> (0..2).each { |i| p i } 0 1 2 => 0..2 irb(main):006:0> (0...2).each { |i| p i } 0 1 => 0...2
余談ですが、0から開始ならInteger#timesも使えます。
irb(main):007:0> 3.times { |i| p i } 0 1 2 => 3
降順のRange
先日、(4..0)
といった事をしてしまいました。ぱっと見できそうな感じなのですが、これだと全くイテレートされません。
irb(main):008:0> (4..0).each { |i| p i } => 4..0
理由は、Rangeのイテレートが、最初の値についてsuccを適用していって最後の値を超えたら*1終わり、という仕様だからです。上記コードの場合は、(4..0).each
の最初の周で、4.succ
つまり5が最後の0を越しているため、一周もしないで終わってしまう、というわけです。
ではどうするか?
Arrayに変換してArray#reverseするのは明らかにアレなので*2、何か無いか探した所、Integer#downtoを使うのが良さげです。
irb(main):021:0> 2.downto(0) { |i| p i } 2 1 0 => 2
ちなみに、また余談ですが、Numberの場合はNumber#stepが使えそうです。
irb(main):023:0> 2.2.step(0, -1) { |i| p i } 2.2 1.2000000000000002 0.20000000000000018 => 2.2
あれ、2.2-1が1.1にならない…何故?浮動小数点の誤差系の問題なんだろうけど、はて。
…まぁ本題ではないので、これについてはまた今度調べるとしましょう。
結論
Integer#downtoを使おう。