#なぜ書くのか
業務でwhileとredoを組み合わせ、条件を満たすまでループをするような実装をし、本番環境でジョブをこかしてしまったので、適切なループを書くためのアウトプット。(paizaとかでもwhileの通り一辺倒になっていたし、、、)
コードレビューの際にワンライナーで書き直すことも結構求められたりしたので、わかる範囲でワンライナーの記法も書いていきます。
#ループの種類と使い所
##for
(Rubyではあまり使わないと思いますが。。。)
for文は指定した範囲オブジェクトの範囲分処理を繰り返したり、配列の要素を順番に取得したい場合に使用します。
※範囲オブジェクトとは「1..3」のように書くことができ、「範囲開始値..範囲終了値」を指定する。
for i in 1..3 do
p "#{i}回目のループ"
end
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
配列の要素を順番に取得することもできますが、あまりforで書く人もいないと思いますし、メリットもないので割愛させていただきます。
##each
Rubyではおなじみeachです。
eachメソッドは配列や範囲オブジェクト、Hash、Enumeratorで使用できるメソッドで、オブジェクトに含まれる要素を順番に取得することができます。
#配列
[1,2,3].each do |i|
p "#{i}回目のループ"
end
#配列ワンライナー
[1,2,3].each {|i| p "#{i}回目のループ"}
#範囲オブジェクト
(1..3).each do |i|
p "#{i}回目のループ"
end
#範囲オブジェクトワンライナー
(1..3).each {|i| p "#{i}回目のループ"}
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
先ほどの「for」より主に「each」が使われます。
##while
続いてwhileです(僕はwhileでやらかしました)
whileでは指定した条件がtrueである間ループします。
for文やeachメソッドは指定した要素分、繰り返し処理を行うのに対して、while文は条件式がfalseになるまで処理を繰り返します。
i = 1
while i <= 3 do #doは省略可能で書かなくても動きます
p "#{i}回目のループ"
i += 1
end
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
eachや今後紹介するtimesなどよりは、リスクがあると考えます。
明確かつ確実な上限が定まっておらず、自身で条件を設定するので、ご利用は計画的に..。
理解して使えば柔軟でめちゃくちゃ使い勝手が良いと思います。
##until
whileの正反対だと考えてください。
あまり使いどころはないかもしれませんが一応。
i = 1
until i > 3 do #doは省略可能で書かなくても動きます
p "#{i}回目のループ"
i += 1
end
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
先ほどとは条件を反対にし、事前に定義したiが、3超えてないかぎり周ります。
わかりにくいのであまりお勧めはしませんが、数値を条件にしなかったりすれば、使い道はありそうですね。
##loop
これはbreakで終了させない限り抜けることはありません。
柔軟な条件は組めますが、これもかなり、リスキーなループだと考えます。
i = 1
loop{
p "#{i}回目のループ"
i += 1
break if i == 4
}
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
定義した変数「i」が、4と等しくなったらbreakするようにしています。
##times
最近頻繁に使うようになったのですが、結構好きです(笑)
指定した回数分回すには最適だと考えます。
#ループ度の数値を使用する場合
3.times do |i|
p i
end
#ワンライナー
3.times {|i| p i}
#=>"0"
#=>"1"
#=>"2"
#ループ度の数値を使用しない場合
3.times do
p "ループだよ3回回るよ"
end
#ワンライナー
3.times {p "ループだよ3回回るよ"}
#=>"ループだよ3回回るよ"
#=>"ループだよ3回回るよ"
#=>"ループだよ3回回るよ"
指定回数回す際には一番明示的で直感的にわかりやすいと思うのでお勧めです。
##upto downto
これもループで回す中で数値が欲しい際によく使います。個人的には好きですね。
uptoは指定した数に到達するまでループで、その間に変数が1ずつ増加し、downtoはuptoの反対で指定した数に到達するまでループで、その間に変数が1ずつ減少します。
#upto
1.upto(3) do |i|
p "#{i}回目のループ"
end
#uptoワンライナー
1.upto(3) {|i| p "#{i}回目のループ"}
#=>"1回目のループ"
#=>"2回目のループ"
#=>"3回目のループ"
#downto
3.downto(1) do |i|
p i
end
#downtoワンライナー
3.downto(1) {|i| p i}
#=>3
#=>2
#=>2
#map
map は、配列の要素の数だけブロック内の処理を繰り返し、結果として作成された配列を返します。map は元の値に対して影響を与えないのに対し、map! は元の値を書き換えます。また、Rubyには collect メソッドがありますが、これはmapメソッドの別名です。
配列内で処理をしその結果を配列として受け取れるなんて便利ですね。
arr = [1, 2, 3]
arr_new = arr.map { |x| x * 2 }
p arr_new
#=>[2, 4, 6]
###eachとの違い
Rubyに慣れてくると何でもかんでもeachで書いてしまいがちだと思います(僕もそうならないよう心がけてはいます)
しかしながら、eachよりもmapを使った方が、すっきりとかけるパターンは意外に多くあります。
#each
int_list = (1..3).to_a
int_to_double = []
int_list.each do |i|
int_to_double << i * 2
end
p int_to_double
#=>[2, 4, 6]
わざわざ新たなリストを生成するためだけに、配列を定義しています。
これがmapを使うことで...
#map
int_list = (1..3)
int_to_double = int_list.map do |i|
i * 2
end
p int_to_double
#ワンライナー
int_list = (1..3)
int_to_double = int_list.map {|i| i * 2}
p int_to_double
#=>[2, 4, 6]
こんなにもすっきりと書けます。
#最後に
またRubyにおける繰り返しの構文やメゾットはまだまだありますので、またタイミングを見て追記させていただきます。
配列に処理を施し、新たな配列を生成したい際にはmapを、
配列の中身を一つずつ取り出し、条件分岐などをしたい際にはeachを
柔軟な条件を組み、ループを回したい際にはwhileを(ご利用は計画的に)
ループの回数があらかじめわかっている際には、timesを
ループを回す中で、1ずつ増える、または減る数値を取得したい際には、upto,downtoを
僕はこんな感じで使い分けています。
ループを回す中でもやりたいことは多種多様なので、一番直感的に、スマートにかけるループを選択し実装しましょう。