いろいろな繰り返し

プログラム中、何回も同じ処理を繰り返すときはこれまで 主に while を利用してきた。そのほかにも用途に応じた 繰り返しの構文がある。次講から頻繁に登場する多数のデータの本格的処理 をスムーズに進めるためには、様々な繰り返し構文をしっかりと理解して自在に 操れるようにしておく必要がある。ここで「繰り返し」のための構文を 整理しておこう。

決められた回数だけ繰り返す

繰り返したい処理の回数だけが重要な場合は、整数に備わっている times メソッドで繰り返す。繰り返したい回数を Nとすると、以下のように記述する。

N.times do
  ……繰り返し本体……
end

以下のプログラムは、timesを使って5回繰り返す例である。

shitsukoi.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

5.times do
  puts "ねえねえ"
  puts "なんだよ"
end

puts "んー、ねえねえ"
puts "なんだっての、しつこいよ!"

このプログラムをshitsukoi.rbというファイル名で保存して 実行してみよう。

% chmod +x shitsukoi.rb
% ./shitsukoi.rb
ねえねえ
なんだよ
ねえねえ
なんだよ
ねえねえ
なんだよ
ねえねえ
なんだよ
ねえねえ
なんだよ
んー、ねえねえ
なんだっての、しつこいよ!

整数を数えながらの繰り返し

たとえば1…10までの和を計算したりするような場合には、 整数を1つずつ増やしていく必要がある。そのような場合には、 これまでのプログラムでは以下のようにしていた。

sum = 0
i = 1
while i <= 10
  sum += i
  i += 1
end
printf("合計は %d です\n", sum)

このループは、要するにiを 1 から上限の数 まで変えながらsumに足していきたいものである。 このような場合は、整数に備わっているメソッド upto を 利用すると以下のようにすっきり書ける。

upto-sum.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

sum = 0
1.upto(10) do |x|
  sum += x
end

printf("合計は %d です\n", sum)

upto とは逆の、1ずつ値を減らしていく downto メソッドもある。

もうひとつ、連続した整数を集合とみなして繰り返すには for整数範囲を用いて以下のように繰り返しを表現する。

for 変数 in 整数範囲
  …繰り返し本体…
end

たとえば、1から10までの数を足すには以下のようにする。

sum = 0
for i in 1..10
  sum += i
end
printf("1から10までの和は%dです\n", sum)

配列要素すべてに対する繰り返し

配列の各要素を1つ1つ取り出しながらの繰り返しには、 each または for を使う。

配列.each do |変数|
  ……上記の変数を使った処理……
end
# または
for 変数 in 配列
  ……上記の変数を使った処理……
end

実際には以下のようになる。

each-sum.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

point = [10, 20, 15, 40, 33]

sum = 0
point.each do |z|
  sum += z
end

printf("合計は %d です\n", sum)

for-sum.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

point = [10, 20, 15, 40, 33]

sum = 0
for z in point do
  sum += z
end

printf("合計は %d です\n", sum)

この場合の変数 z には、point 配列の 各要素の値が順次代入される。一回目は z=10、 二回目は z=20、三回目は z=15、 四回目は z=40、五回目は z=33、 と代入された上でループ内部が実行される。

上記の例では10, 20, 15, 40, 33が全てsumに足されるので

合計は 118 です

と出力される。

この場合、配列の先頭から 値が拾われて変数に代入されるが、別の種類の配列では順番はどうなるか わからない。取りだす順番を気にしなくてよい場合にこれを使う。 取りだす順番を確実にしたい場合は、

配列.sort.each do |変数|
  ……上記の変数を使った処理……
end

のように、配列sortした結果を与える。

問題: では取りだす順番を逆順に並べ替えたものにしたい場合は どうしたらよいか。

飛び飛びの繰り返し

1, 3, 5, 7, 9, ... とか、10, 20, 30, ... のように、数値を 間隔一定で変えながら利用したい場合は、整数に備わっている step メソッドを利用する。書式は以下のとおり。

開始値.step(終了値[, 増加値]) do |変数|
 …変数を利用した処理
end

増加値 は省略できて、省略した場合は1とみなされる。 実際の利用例は以下のようになる。

数値で繰り返すときの注意事項

次の二つのプログラムを実際に実行してみよう。

countdown-int.rb

#!/usr/koeki/bin/ruby
# coding: utf-8
c=10
while c != 0
  printf("%d\n", c)
  c -= 1
  sleep 0.7
end
printf("おしまい\n")

こちらは予想どおりの結果となる。

10
9
8
7
6
5
4
3
2
1
おしまい

10から0までの整数の繰り返しを、1.0から0.0までの小数繰り返しに 変えたプログラムを動かしてみる。

countdown-float.rb

#!/usr/koeki/bin/ruby
# coding: utf-8

c=1.0
while c != 0.0
  printf("%f\n", c)
  c -= 0.1
  sleep 0.7
end
printf("おしまい\n")

期待に反して、実行すると以下のようになる。

1.000000
0.900000
 :
…中略…
 :
0.100000
0.000000
-0.100000
-0.200000
-0.300000
-0.400000
^Ccountdown-float.rb:7:in `sleep': Interrupt
        from countdown-float.rb:7

whileループでは、「cが0.0と等しくないなら繰り返せ」 という条件を指定しているのに、実際にはc=0.000000のときにループが終了して いない。

これは、計算機による小数計算の誤差に起因する。計算機では内部で2進数を 用いているので小数を表すときに限られた桁数では表し切れないことがある。 3分の1を10進数で表そうとすると 0.333333... となって、途中で切り捨てなけ ればならないのと同様、10進数の0.1を2進数で表すと循環小数になり途中で切り 捨てなければならなくなる。そのため0.1を10回積み重ねても、切り捨て誤差も 積み重なって正確な1.0にはならない。それゆえ、1.0から0.1を10回引いたものは 厳密な0.0にはならないのである。

数値を使ってループを作るときは、ループ変数に浮動小数点数を使うのは避 けなければならない。どうしても小数刻みのものが欲しければ、整数を割り算し て利用する。たとえば、1.0から0.0でカウントダウンしたければ

countdown-f2.rb

#!/usr/koeki/bin/ruby
# coding: utf-8
c=10
while c != 0
  printf("%f\n", c/10.0)
  c -= 1
  sleep 0.7
end
printf("おしまい\n")

のように、整数のループ変数を割って利用する。


本日の目次