Ruby 入門

Ruby break と next ループコントロール

Ruby において、break および next キーワードはループのエグゼキュート(実行)に対して強力なコントロールを提供し、特定のコンディションに基づいて一部のイテレーションをスキップしたり、ループ全体を完全にエグジット(脱出)したりすることを可能にします。

1. ループにおける break の理解

break キーワードは、ループをアーリー・ターミネート(早期終了)するためにユースされます。Ruby がループ内部で break ステートメントにエンカウント(遭遇)すると、即座に該当ループをエグジットし、コントロールをループ終了後の次のステートメントへトランスファー(移行)します。

1.1 ベーシックな break のサンプル

あるコンディションを満たすまでイテレーションを継続する while ループを想定します:

i = 0
while i < 10
  puts i
  i += 1
  if i == 5
    break # i が 5 に等しい場合、ループをエグジットする
  end
end
puts "ループが終了しました!"

このサンプルでは、ループは 0 から 4 までのナンバーをプリントします。i が 5 になった時点で break ステートメントがトリガーされ、ループがターミネートします。アウトプットのリザルトは以下の通りです:

0
1
2
3
4
ループが終了しました!

1.2 for ループにおける break

break キーワードのビヘイビア(振る舞い)は、for ループにおいても同様です。

for i in 0..9
  puts i
  if i == 3
    break # i が 3 に等しい場合、ループをエグジットする
  end
end
puts "ループが終了しました!"

このループは 0 から 3 までのナンバーをプリントした後、エグジットします:

0
1
2
3
ループが終了しました!

1.3 each イテレーターにおける break

breakeach などのイテレーターとコンバイン(結合)してユースすることも可能です。

(1..5).each do |number|
  puts number
  if number == 3
    break # number が 3 に等しい場合、ループをエグジットする
  end
end
puts "ループが終了しました!"

アウトプット:

1
2
3
ループが終了しました!

1.4 ネストされたループにおける break

ネストされたループを処理する際、break はそれがコールされた最もインナー(内側)のループのみをエグジットします。アウター(外側)のループは影響を受けません。

for i in 1..3
  for j in 1..3
    puts "i: #{i}, j: #{j}"
    if i == 2 && j == 1
      break # i が 2 であり、かつ j が 1 である場合、インナーループをエグジットする
    end
  end
end
puts "ループが終了しました!"

アウトプットのリザルトは以下の通りです:

i: 1, j: 1
i: 1, j: 2
i: 1, j: 3
i: 2, j: 1
i: 3, j: 1
i: 3, j: 2
i: 3, j: 3
ループが終了しました!

注目すべき点として、i が 2 で j が 1 の時、インナーループはターミネートしましたが、アウターループは依然として i = 3 のケースを継続してエグゼキュートしています。

2. ループにおける next の理解

next キーワードは、カレントのループ・イテレーションの残りの部分をスキップし、直接次のイテレーションへエンター(突入)するためにユースされます。特定のコンディション下で一部のコードブロックのエグゼキュートをアボイド(回避)しつつ、ループ全体は継続してランさせたい場合に非常に有用です。

2.1 ベーシックな next のサンプル

偶数のみをプリントする while ループを考慮してみます:

i = 0
while i < 10
  i += 1
  if i % 2 != 0
    next # 奇数をスキップする
  end
  puts i
end
puts "ループが終了しました!"

このサンプルにおいて、もし i が奇数(すなわち i % 2 != 0 が true)であれば、next ステートメントがエグゼキュートされ、後続の puts i を直接スキップしてループの次のイテレーションへとエンターします。アウトプットのリザルトは以下の通りです:

2
4
6
8
10
ループが終了しました!

2.2 for ループにおける next

next キーワードの機能は for ループにおいても同一です。

for i in 0..9
  if i % 2 != 0
    next # 奇数をスキップする
  end
  puts i
end
puts "ループが終了しました!"

このループは 0 から 9 までの偶数のみをプリントします:

0
2
4
6
8
ループが終了しました!

2.3 each イテレーターにおける next

next もまた each などのイテレーターと一緒にユースできます。

(1..5).each do |number|
  if number % 2 != 0
    next # 奇数をスキップする
  end
  puts number
end
puts "ループが終了しました!"

アウトプット:

2
4
ループが終了しました!

2.4 ネストされたループにおける next

ネストされたループにおいて、next はそれがコールされた最もインナーのループのカレント・イテレーションにのみ影響を与えます。

for i in 1..3
  for j in 1..3
    if i == 2 && j == 2
      next # i が 2 であり、かつ j が 2 である場合スキップする
    end
    puts "i: #{i}, j: #{j}"
  end
end
puts "ループが終了しました!"

アウトプットのリザルトは以下の通りです:

i: 1, j: 1
i: 1, j: 2
i: 1, j: 3
i: 2, j: 1
i: 2, j: 3
i: 3, j: 1
i: 3, j: 2
i: 3, j: 3
ループが終了しました!

注目すべき点として、i が 2 で j が 2 の時、プリントのステートメントはスキップされましたが、インナーループは直接次のステップである j = 3 へと進み、エグゼキュートを継続しています。

3. 実践的なユースケースのデモンストレーション

さらに実践的な意義を持つシナリオをいくつか探索し、breaknext をどのように高効率に運用するかを見ていきましょう。

3.1 ケース1:アレイ内のエレメントのサーチ

アレイ(Array)内の特定のエレメントをサーチし、リソースをセーブするために、該当エレメントが見つかった時点で即座にサーチをストップしたいと仮定します。

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
target = 5

numbers.each do |number|
  puts "チェック中: #{number}"
  if number == target
    puts "見つかりました: #{target}"
    break # ターゲットを見つけた後、ループをエグジットする
  end
end

puts "サーチが完了しました。"

アウトプット:

チェック中: 1
チェック中: 2
チェック中: 3
チェック中: 4
チェック中: 5
見つかりました: 5
サーチが完了しました。

3.2 ケース2:有効なデータの処理(無効な値のスキップ)

ファイル内のデータを処理しており、その中のいくつかのデータアイテムが無効(例えば nil)である可能性があると想像してください。next をユースしてこれらの無効なエントリーをスキップし、有効なデータ処理を継続させることができます。

data = [1, 2, nil, 4, 5, nil, 7, 8, 9, 10]

data.each do |value|
  if value.nil?
    puts "nil 値をスキップします"
    next # nil 値をスキップする
  end
  puts "処理中: #{value * 2}"
end

puts "データ処理が完了しました。"

アウトプット:

処理中: 2
処理中: 4
nil 値をスキップします
処理中: 8
処理中: 10
nil 値をスキップします
処理中: 14
処理中: 16
処理中: 18
処理中: 20
データ処理が完了しました。

3.3 ケース3:ページネーション機能の実装

ページネーション(Pagination)のロジックを実装し、各ページに一定数のアイテムをディスプレイしたいと考えます。break は各ページにディスプレイするアイテム数をリミットするためにユースでき、next は前のページのデータをスキップするためにユースできます。

items = (1..25).to_a
page_size = 10
current_page = 1

start_index = (current_page - 1) * page_size
end_index = start_index + page_size - 1

items.each_with_index do |item, index|
  if index < start_index
    next # カレントページより前のアイテムをスキップする
  end
  
  puts "アイテム: #{item}"
  
  if index >= end_index
    break # 必要な数のアイテムをディスプレイした後にストップする
  end
end

puts "第 #{current_page} ページのディスプレイが完了しました。"

アウトプット:

アイテム: 1
アイテム: 2
アイテム: 3
アイテム: 4
アイテム: 5
アイテム: 6
アイテム: 7
アイテム: 8
アイテム: 9
アイテム: 10
第 1 ページのディスプレイが完了しました。