【Ruby入門説明書】break,next,redoの違いについて詳しく解説
Ruby繰り返し処理 break next redo
forやwhile、eachなど、Rubyにはいくつもの繰り返し処理を表現する方法があります。
それらはそれぞれ、使用に適したシーンがあり、きちんと使い分けることで、プログラムの可読性(読みやすさ)を上げて不具合を防ぎ、再利用もしやすくできます。
これらの繰り返し処理を制御するのに使うのが、break,next,redoです。
break,next,redoを使うことで、繰り返しを中断したり、処理を飛ばしたり、やり直したりすることができます。
そんな、break,next,redoについて、詳しく解説していきましょう。
whileの中にwhileが入っていて、一見ややこしそうなプログラムですが、外のwhileでカウントアップ、内側のwhileではカウントダウンしながら、「()」を表示するプログラムです。
この状態では、break,next,redoの処理はすべてコメントアウトされていますので、すべての繰り返し処理を行い、以下のような結果になります。
[bash]()()()()()()()()()()–10
()()()()()()()()()–9
()()()()()()()()–8
()()()()()()()–7
()()()()()()–6
()()()()()–5
()()()()–4
()()()–3
()()–2
()–1
[/bash]
この結果が、break,next,redoの処理を有効にすることによってどう変わるのか、想像しながら読み進めていただければと思います。
break
繰り返し処理の中には、集計のように決められた回数必ず処理しなければいけない場合と、検索のように結果が出たらそこで止めても良い場合があります。
後者の場合に利用するのが、breakです。
Rubyは、繰り返し処理の中でbreakに到達すると、ただちに繰り返し処理の次のステップへと処理を移します。
その結果、繰り返し処理は終了してしまうわけです。
メリット
不要な繰り返し処理を行わないことで、処理の効率が上げり、プログラムのレスポンスが良くなります。
breakがなければプログラムしにくいロジックもあります。
デメリット
繰り返し処理の中で変化していた変数などは、breakの直後の状態になっています。ほとんどの場合は問題ないとは思いますが、もし後処理が必要なもの(例えばファイル操作では、開いたファイルを閉じなければいけません)については、breakすることで後処理がされないことになる可能性がありますので、注意が必要です。
動かしてみる
break01.rbの(1)のコメントを外して動かしてみましょう。
(1)は内側のwhile の中で、カウントダウンされているjが5よりも小さくなると、繰り返し処理を抜けます。つまり、jが5よりも小さくなると、「()」が表示されないわけです。
どう動くのか、想像してから動かしてみましょう。
(結果)
[bash]()()()()()–10
()()()()–9
()()()–8
()()–7
()–6
–5
–4
–3
–2
–1
[/bash]
next
繰り返し処理の中には、どれだけ長い処理があってもかまいません。そのため、プログラムによっては繰り返し処理の中で様々なことを行います。
そうすることで、あちこちで繰り返すことを避け、プログラム全体の効率を上げているわけです。
しかし、特定の条件の場合等には、それ以降の処理を行わずに繰り返しの先頭へ移行して、次の繰り返しへ進みたい場合もあるでしょう。
そんなときに利用するのが、nextです。
Rubyは、繰り返し処理の中でnextに到達すると、以降の処理をすべて飛ばして、繰り返し処理の先頭へ移行し、次の繰り返し処理を開始します。
つまり、繰り返し処理を次(next)に進めるわけです。
メリット
nextのメリットは、処理を一気に飛ばしてしまえることです。
例えば、配列に様々なオブジェクトが格納されている場合、特定のオブジェクトの場合だけ処理をするプログラムを作成し、そのオブジェクト以外はnextで処理を飛ばしてしまえば、余計な処理を作成する必要がありません。
なにより、余計な処理が行われませんので効率も良いでしょう。
デメリット
breakと同様に、後処理を忘れないようにしなければいけません。
ただし、breakと違って繰り返し処理は行います。つまり、何度も後処理忘れを繰り返してしまうことになりますので、メモリ不足など原因特定の難しい不具合の元になる可能性があります。
動かしてみる
break01.rbの(2)のコメントを外して動かしてみましょう。
(2)は外側のwhile の中で、内側のwhileのカウンタであるjが5で割り切れると、以降の処理を無視します。
実際に動かして結果を確認してから、プログラムコードを見直してみるのも、良い勉強になります。
(結果)
[bash]()()()()()()()()()–9
()()()()()()()()–8
()()()()()()()–7
()()()()()()–6
()()()()–4
()()()–3
()()–2
()–1
[/bash]
redo
繰り返し処理を続けている中で、すべてをリセットしてやり直すしかない状況になる場合もわずかながらに存在します。
(通信のネットワークエラーなどは、最初からやり直すことでスムーズに進むことがあります)
そんな場合に、繰り返し処理をやり直すことができるredoが便利です。
繰り返し処理の中でredoがあると、Rubyは繰り返し処理を中断して、最初からやり直します。
メリット
繰り返し処理を作る上で、最初からやり直すシーンはそれほどないかもしれませんが、やり直す処理をredoなしで作ろうとすると、別の繰り返し処理を必要とするなど、複雑なプログラムになることでしょう。それをbreakやnextのようにすっきりと記載できるのは、大きなメリットです。
デメリット
繰り返し処理を最初からやり直すといっても、後処理が必要なものまでなかったことにはなりませんので、breakやnextと同じ後処理忘れには気を付けなければいけません。
また、それに加えて、やり直す条件が必ず成り立たなくなるようにするか、breakなどで繰り返し処理を中断する処理を組み込んでおかなければ、簡単に無限ループになってしまいますので、安易な使用はたいへん危険です。
動かしてみる
break01.rbの(3)のコメントを外して動かしてみましょう。
(3)は内側のwhile の中で、カウントダウンしているjが7よりも大きい間、繰り返し処理を一旦やめてやり直しています。つまり、jが7になるまで、redoの後ろの処理は行われません。
どんな図形が表示されるでしょうか?
(結果)
[bash]()()()()()()()()–10
()()()()()()()()–9
()()()()()()()()–8
()()()()()()()–7
()()()()()()–6
()()()()()–5
()()()()–4
()()()–3
()()–2
()–1
[/bash]
break,next,redoの違い
break01.rbで実際の動作を確認しましたが、break,next,redoは、それぞれ繰り返しの制御の仕方が違っています。
改めて、break,next,redoの違いをまとめると、以下のようになります。
・breakは、繰り返し処理を中断する
・nextは、以降の処理をスキップして、繰り返し処理を継続する
・redoは、繰り返し処理を中断して、最初からやり直す
この違いを理解しておけば、繰り返しの処理を柔軟に効率よく作っていくことができますので、憶えておきましょう。
break,next,redoの使いどころ
break,next,redoの動きを理解したところで、どう使うかというのが分からなければ、意味がありません。
特にこの3つは繰り返し処理の流れを大きく変え、ときには無限ループまでも引き起こしてしまうような力を持っています。
そのため、基本的な使いどころくらいは理解した上で使っていきたいものです。
whileやunlessには、なくてはならない相棒?
breakやnext,redoは、繰り返し処理の処理を行うかどうかを制御するのに使います。
そのため、あらかじめ処理の回数が決まっているforやeachでの使用は、限定的になるかもしれません。(もちろん、プログラムの効率化のために余計な処理を省くことができますので、とても有意義です)
つまり、breakやnext,redoの真価が発揮されるのは、whileやunlessなどの「回数が決まっていない」繰り返し処理になるわけです。
whileやunlessは、もちろん繰り返しが終わる条件を設定できます。しかし、あまり複雑な条件を設定するのは可読性を犠牲にすることになりますので、推奨できません。
そのため、最低限の終了条件はwhileやunlessで設定し、その他の条件で繰り返し処理を制御するのにbreakやnextを利用するわけです。
普通のプログラムでは必須
実際にユーザーが使用するプログラムで、処理が完了すると終了するようなプログラムは稀です。
むしろ、常に動いており何らかの条件が成立したら(終了ボタンが押されるなど)終了する、というものが一般的でしょう。
そのため、多くのプログラムで敢えて「無限ループ」が実装されています。
そして、その無限ループの中で、終了ややり直しなどを行うために必要なのが、breakやnext、redoなのです。
(※breakやnextがなければ、今のプログラムの形を記載するのは、とても面倒で読みにくいものになることでしょう)
つまり、無限ループから抜けたり、無限ループ内での流れを変えたりするための道具として、breakやnextを使うわけです。
break,next,redoは、ifと組み合わせて
繰り返し処理の中でbreak,next,redoが現れると、それ以降に書かれている処理は無視されます。
つまり、これらが何の条件もなく現れることはありえないと考えても間違いではないでしょう。
そのため、break,next,redoは、ifなどの条件分岐と組み合わせ、一定の条件が成り立った(成り立たなかった)場合のみ実行されるように作るのが一般的です。
(※break01.rbでも、breakやnext、redoはifとセットで使用しています)
breakに引数を渡せる
そもそも、Rubyではforにも戻り値があります。
これは、他の言語からRubyに移行した人にはとても不自然に感じることかもしれませんが、Rubyではごく自然なことです。
具体的に見ていきましょう。
break02-1.rb
ret = for i in 0..9 result = (i * i).to_s + " " print(result) end puts print(ret)
(結果)
[bash]0 1 4 9 16 25 36 49 64 81
0..9
[/bash]
このように、forの戻り値をretで受け取ることに対して何のエラーも発生せず、最後には内容を表示することもできています。
(※forの戻り値は繰り返しの元になるオブジェクトがそのまま返されます)
じつは、breakはこの繰り返し処理の戻り値を操作することができるのです。
戻り値の操作といってもよく分からないと思いますので、break02-1.rbにbreakを使って途中で繰り返しを抜けるようにしてみましょう。
break02-2.rb
ret = for i in 0..9
result = (i * i).to_s + ” “
if i * i > 50
break
end
print(result)
end
puts
print(ret)
(結果)
[bash]0 1 4 9 16 25 36 49 [/bash]
結果の1行目で、50の直前まで出力されていますので、breakが動いて繰り返し処理が完了していることが分かると思います。
ポイントは、2行目が空白になっているところです。
これは、じつはbreakが返している値です。
もっと分かりやすくするために、breakに引数を渡してみます。
break02-3.rb
ret = for i in 0..9
result = (i * i).to_s + ” “
if i * i > 50
break(“break!”)
end
print(result)
end
puts
print(ret)
(結果)
[bash]0 1 4 9 16 25 36 49
break!
[/bash]
そうです。breakに渡している引数が、そのまま繰り返し処理の戻り値に上書きされているのです。
つまり、break02-2.rbでは、breakに引数が渡されていないから、繰り返し処理の戻り値も何も表示されなかったわけです。
これは、forだけではなくeachでも使えますので、繰り返し処理を1つの処理ブロックとしてプログラムを作ることができますので、利便性の高い機能といえます。
じつはnextにも引数を渡せる
breakと同様に、nextにも引数を渡すことができます。
nextの引数も、基本的にはbreakと同じで、戻り値に影響します。しかし、breakが繰り返し処理終了時の戻り値であるのに対し、nextの場合は繰り返し処理の1周終了時の戻り値になるところが大きく違います。
そのため、break02のようにfor文では意味をなしません。
引数付きのnextがその真価を発揮する代表的な繰り返し処理は、繰り返しごとの処理結果を配列として戻り値を返すmapメソッドです。
例えば、以下のように3で割り切れる場合は処理を飛ばしてしまう場合を考えましょう。
break03-1.rb
list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ret = list.map do |num|
next if num % 3 == 0
num * num
end
print(ret)
(結果)
[bash][1, 4, nil, 16, 25, nil, 49, 64, nil, 100]
[/bash]
結果のように、3で割り切れる要素は処理をしていませんので、無効値(nil)が格納されてしまっています。
こういった場合に、引数付きnextが役立ちます。
処理をしませんので、引数付きnextを使って、0を格納するようにしましょう。
break03-2.rb
list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
ret = list.map do |num|
next 0 if num % 3 == 0
num * num
end
print(ret)
(結果)
[bash][1, 4, 0, 16, 25, 0, 49, 64, 0, 100]
[/bash]
先ほどnilだった場所に0が格納されました。これで、処理がぐっと楽になると思います。
他言語のcontinueと違って、繰り返し処理を中断しているわけではない
以上のように、Rubyのbreakやnextは、「戻り値を設定する」という機能を持っています。
これはつまり、多くのサイトや書籍で他言語のbreakやcontinueと同じようなものと説明されていますが、じつは内部的にまったく違っているということを理解しておきましょう。
他言語のbreakやcontinueは、本当に処理の流れを書き換えている場合がほとんどです。(「次はどの命令を実行するか」を示している値を直接書き換えています)
しかし、Rubyのbreakやnextは、「戻り値を返している=処理を完了の機能を実行している」ということになります。
この理解があれば、breakやnextに引数があることに違和感を覚えることはなくなるでしょう。
もちろん、プログラムを作る上で、その動きはほぼ同じですので、多くのサイトや書籍が噓を伝えているわけではありません。
breakpointで検索してきた人へ
最後に、本稿を訪れる人の中の「breakpoint」で検索してきた人へ向けての補足を書いておきます。
本稿で説明しているbreakは、あくまでも繰り返し処理を終了するbreakメソッドについてです。
「breakpoint」とは、まったく違うものです。
「breakpoint」というのは、ほぼすべてのデバッガにある機能で、デバッグ時にプログラムを一時停止するポイントのことを意味します。
この「breakpoint」が設定された状態で、デバッガ上でプログラムを動かすと、プログラムがbreakpointに達した瞬間にプログラムは停止し、そのときの変数の状態などを確認、変更することができるようになります。
この機能はプログラムの動きを解析する上でとても便利な機能ですので、ぜひ積極的に使うことをおすすめします。
なお、「【Ruby入門説明書】stepについて解説」で紹介しているRuby標準のデバッガにも搭載されており、紹介していますので、ご参照いただけますと幸いです。
まとめ
長くなってしまいましたが、break,next,redoの解説は以上です。
break,next,redoは、機能的にあまり目立つことはなく、簡単に説明されていることが多いですが、うまく使えばプログラムの効率を上げ、プログラムコードをすっきりと見やすくしてくれる効果があります。
その反面、使い方を誤ると意図しない無限ループに陥ることもある両刃の剣といえるものです。
もちろん、深く追求すればもっと詳しく書けますが、文章では長くなりますので、これ以上のことを知りたければ、プログラム教室などで直接講師に聞いてみることをおすすめします。こういった目立たないTIPSは、プログラムをかじったことのある人は大好物ですので、いろいろと教えてくれることでしょう。
・breakは、繰り返し処理を中断する
・nextは、以降の処理をスキップして、繰り返し処理を継続する
・redoは、繰り返し処理を中断して、最初からやり直す
・無限ループを作ってしまう恐れがあるので、しっかり設計すること
・無限ループを抜ける仕組みには、breakが必須
・「引数」は、処理に渡すデータ(値や文字列など)のこと
・「戻り値」は、処理結果として返ってくるデータのこと
・breakの引数は、繰り返し処理の戻り値になる
・nextの引数は、繰り返し処理1周分の戻り値になる
・breakpointはプログラムを一時停止するデバッガの機能およびそのポイントのこと