for / each(繰り返し)(Ruby)
『Ruby』の繰り返し構文には『for』文と『each』メソッドの2種類があります。動作は似ていますが、ブロック変数のスコープや書き方に違いがあります。
構文
『for』文の構文です。
# 配列の各要素に対して処理します。 for 変数 in 配列 処理 end # 範囲オブジェクトと組み合わせても使えます。 for 変数 in 開始..終了 処理 end
『each』メソッドの構文です。
# ブロックを渡して繰り返します({} 形式)。
配列.each { |変数| 処理 }
# 複数行のブロックは do...end で書きます。
配列.each do |変数|
処理
end
for / each の比較
| 項目 | for 文 | each メソッド |
|---|---|---|
| ブロック変数のスコープ | ループ終了後も変数が残ります。 | ブロック変数はブロックの外から参照できません。 |
| 戻り値 | 繰り返した配列(または範囲)を返します。 | レシーバの配列をそのまま返します。 |
| Rubyらしさ | 他言語からの移行者に馴染みやすい書き方です。 | Rubyの慣用的な書き方として広く使われています。 |
| ブロック構文 | do...end / {} ブロックは使いません。 | do...end または {} ブロックを使います。 |
| next / break | どちらも使えます。 | どちらも使えます。 |
times / upto / downto との使い分け
| 構文・メソッド | 用途 |
|---|---|
| for 変数 in 配列 | 配列や範囲をループするときに使います。 |
| 配列.each { |e| } | 配列の要素を順番に処理するときに使います。Rubyで最もよく使われる書き方です。 |
| n.times { |i| } | 決まった回数だけ繰り返すときに使います。インデックスは0始まりです。 |
| start.upto(stop) { |i| } | 特定の整数範囲を昇順に繰り返すときに使います。 |
| start.downto(stop) { |i| } | 特定の整数範囲を降順に繰り返すときに使います。 |
サンプルコード
『for』文でキャラクター名を出力します。ループ後も変数『name』が残ることを確認します。
for_loop.rb
characters = ['桐生一馬', '真島吾朗', '澤村遥', '秋山駿', '冴島大河']
# for 文で各キャラクターの名前を出力します。
for name in characters
puts "キャラクター: #{name}"
end
# ループ終了後も変数 name が残っています。
puts "最後の name: #{name}" # => 冴島大河
ruby for_loop.rb キャラクター: 桐生一馬 キャラクター: 真島吾朗 キャラクター: 澤村遥 キャラクター: 秋山駿 キャラクター: 冴島大河 最後の name: 冴島大河
『each』メソッドで同じ処理を書きます。ブロック変数のスコープが外に漏れないことを確認します。
each_loop.rb
characters = ['桐生一馬', '真島吾朗', '澤村遥', '秋山駿', '冴島大河']
# each メソッドでブロック変数を使って出力します。
characters.each do |name|
puts "キャラクター: #{name}"
end
# ブロック変数 name はここでは参照できません。
# puts name # => NameError: undefined local variable or method 'name'
puts "each 完了"
ruby each_loop.rb キャラクター: 桐生一馬 キャラクター: 真島吾朗 キャラクター: 澤村遥 キャラクター: 秋山駿 キャラクター: 冴島大河 each 完了
『for』文と範囲オブジェクトを組み合わせて、戦闘力ランキングを出力します。
for_range.rb
characters = ['桐生一馬', '真島吾朗', '澤村遥', '秋山駿', '冴島大河']
powers = [9800, 9500, 7200, 8100, 9100]
# for 文で範囲を使ってインデックスを制御します。
for i in 0..4
puts "#{i + 1}位: #{characters[i]}(戦闘力 #{powers[i]})"
end
ruby for_range.rb 1位: 桐生一馬(戦闘力 9800) 2位: 真島吾朗(戦闘力 9500) 3位: 澤村遥(戦闘力 7200) 4位: 秋山駿(戦闘力 8100) 5位: 冴島大河(戦闘力 9100)
『times』で5回繰り返し、『upto』で昇順に、『downto』で降順に出力します。
times_upto_downto.rb
characters = ['桐生一馬', '真島吾朗', '澤村遥', '秋山駿', '冴島大河']
# times: 5回繰り返してインデックスで参照します。
puts "=== times ==="
5.times { |i| puts "#{i}: #{characters[i]}" }
# upto: 0から4まで昇順に繰り返します。
puts "=== upto ==="
0.upto(4) { |i| puts "#{i}: #{characters[i]}" }
# downto: 4から0まで降順に繰り返します。
puts "=== downto ==="
4.downto(0) { |i| puts "#{i}: #{characters[i]}" }
ruby times_upto_downto.rb === times === 0: 桐生一馬 1: 真島吾朗 2: 澤村遥 3: 秋山駿 4: 冴島大河 === upto === 0: 桐生一馬 1: 真島吾朗 2: 澤村遥 3: 秋山駿 4: 冴島大河 === downto === 4: 冴島大河 3: 秋山駿 2: 澤村遥 1: 真島吾朗 0: 桐生一馬
概要
『for』文と『each』メソッドはどちらも配列や範囲を繰り返すための構文ですが、ブロック変数のスコープが異なります。『for』文のループ変数はループ終了後も参照でき、意図せず外側で使われる可能性があります。『each』のブロック変数はブロック内のみ有効で、外側からは参照できません。変数の意図しない漏れを防ぐために、Rubyでは『each』を使うスタイルが広く使われています。
単純な回数繰り返しには『times / upto / downto』が向いています。配列の要素を変換したい場合は『map / collect』が向いています。インデックスも同時に使いたい場合は『each_with_index』が便利です。
よくあるミス
ミス1: forのスコープ漏れで意図しない挙動
『for』文のループ変数はループ終了後も外側のスコープに残ります。ループ後に同名の変数を使うと、意図せず最後のループ値が参照されます。
for_scope_ng.rb
characters = ["桐生一馬", "真島吾朗", "澤村遥"] for name in characters puts name end puts name
ruby for_scope_ng.rb 桐生一馬 真島吾朗 澤村遥 澤村遥
ループ変数が外に漏れていることで、後続のコードで想定外の値が使われることがあります。『each』ではブロック変数がブロック内に閉じているため、この問題は発生しません。
for_scope_ok.rb
characters = ["桐生一馬", "真島吾朗", "澤村遥"] characters.each do |name| puts name end puts defined?(name).inspect
ruby for_scope_ok.rb 桐生一馬 真島吾朗 澤村遥 nil
ミス2: each内でbreakするとnilが返る
『each』は通常、レシーバの配列をそのまま返します。しかし、ブロック内で『break』を使うと、eachはnilを返します(breakに値を渡すとその値を返すこともできます)。戻り値をあてにしているコードでは注意が必要です。
each_break_ng.rb
characters = ["桐生一馬", "真島吾朗", "澤村遥", "秋山駿", "冴島大河"] result = characters.each do |name| break if name == "澤村遥" puts name end puts result.inspect
ruby each_break_ng.rb 桐生一馬 真島吾朗 nil
breakに値を渡すとその値を返せます。特定の要素を見つけて返したい場合は『find / detect』が向いています。
each_break_ok.rb
characters = ["桐生一馬", "真島吾朗", "澤村遥", "秋山駿", "冴島大河"]
found = characters.find { |name| name == "澤村遥" }
puts found.inspect
ruby each_break_ok.rb "澤村遥"
ミス3: eachの戻り値をmapと混同する
『each』の戻り値はレシーバの配列そのままです。変換した結果を新しい配列として受け取りたい場合は『map』が必要です。eachの戻り値を変換結果だと思ってしまうミスがよく見られます。
each_vs_map_ng.rb
characters = ["桐生一馬", "真島吾朗", "冴島大河"]
result = characters.each do |name|
"#{name}(龍が如く)"
end
puts result.inspect
ruby each_vs_map_ng.rb ["桐生一馬", "真島吾朗", "冴島大河"]
元の配列がそのまま返っています。ブロックの戻り値は捨てられます。変換結果を新しい配列にしたい場合は『map』を使います。
each_vs_map_ok.rb
characters = ["桐生一馬", "真島吾朗", "冴島大河"]
result = characters.map do |name|
"#{name}(龍が如く)"
end
puts result.inspect
ruby each_vs_map_ok.rb ["桐生一馬(龍が如く)", "真島吾朗(龍が如く)", "冴島大河(龍が如く)"]
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。