本記事では、Rubyの配列とループ処理を扱います。
Rubyには、ブロックという特徴的な文法があり、
ループ処理もそれに関連してか、
他のプログラミング言語よりもメソッドがたくさんあります。
本記事ではそんなループ処理や配列について扱います。
下記の動画でRubyの文法全般について、
動画一本で解説しているので、
良ければご参考ください。
配列
まずは、配列からご紹介します。
配列は、他のプログラミング言語にも大抵ある、
複数のデータを格納できるオブジェクトやデータ構造にあたります。
以前紹介したTypeScriptの記事でも、
配列は出てきましたが、大まかには、
そこまで大差ないので、そちらも参考にするとわかりやすいかもしれません。
(参考記事)
記法としてはシンプルで、
基本は他のプログラミング言語と大差ありません
irb(main):003:0> arr = [1,2,3]
=> [1, 2, 3]
irb(main):004:0> arr[1]
=> 2
irb(main):005:0> arr[0]
=> 1
添字は0スタートで、
arr[0]で配列の最初の要素を取得しています。
負の数を指定することもできて、
その場合、後ろから数えます。
irb(main):006:0> arr[-1]
=> 3
存在しない要素を指定してもエラーにはなりませんが、
存在しない要素に指定して値を代入した場合、負の数を添え字にしたときだけ、
エラーが起きます。
そして、正の数を添え字にして、存在しない要素に値を代入した場合、
問題なく代入できるが、指定した添え字と配列の最後の要素数に開きがある場合、
間の部分はすべてnilで埋め尽くされます。
irb(main):014:0> arr
=> [2, 3, 4, 5, 10]
irb(main):015:0> arr[-10] #存在しないのでnil
=> nil
irb(main):016:0> arr[10] #存在しないのでnil
=> nil
irb(main):017:0> arr[10] = 10
=> 10
irb(main):018:0> arr #arr[10]に代入した際、その間はnilで埋められる
=> [2, 3, 4, 5, 10, nil, nil, nil, nil, nil, 10]
irb(main):021:0> arr[-30] = 10 #添え字が正ならエラーにならないが負ならエラー
Traceback (most recent call last):
4: from /Users/reisuta/.rbenv/versions/2.7.7/bin/irb:23:in `<main>'
3: from /Users/reisuta/.rbenv/versions/2.7.7/bin/irb:23:in `load'
2: from /Users/reisuta/.rbenv/versions/2.7.7/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
1: from (irb):21
IndexError (index -30 too small for array; minimum: -11)
他のプログラミング言語とかだと、
存在しない要素を添え字にしたら、
エラーになったり、代入したりできなかったりするので、
このあたりは、例外をあまり出さない自由なRubyらしさを感じますね。
Rubyの場合、他のプログラミング言語とかで
例外をはきそうなことをしても、
問題なかったりするので、どういうときにエラーが起きるのかを、
把握しておく必要があるというのが、
自由なRubyの難しさだなと感じます。
<<で要素を追加する
配列に要素を追加したいときは、<<を使います。
他のプログラミング言語ではあまり見られないRubyっぽい記法として
<<で配列の最後に要素を追加するんですね。
irb(main):006:0> arr << 19
=> [1, 2, 3, 19]
irb(main):007:0> arr
=> [1, 2, 3, 19]
ちなみに、pushメソッドや上記で紹介した直接添え字を指定しても、
要素の追加は可能です。
irb(main):024:0> arr = [1,2,3]
=> [1, 2, 3]
irb(main):025:0> arr.push(4)
=> [1, 2, 3, 4]
irb(main):026:0> arr[5] = 5
=> 5
irb(main):027:0> arr
=> [1, 2, 3, 4, nil, 5]
ちなみに要素の変更も追加と同じように
値を代入してできます。
Rubyの特徴として、
同じことをする方法が複数存在するというのがあります。
たとえば、上記だと << とpushメソッドなどですね。
これ以外にも、Rubyにはaliasメソッドというものもあるので、
どれを使えば、迷いがちなのが、
他のプログラミング言語ではあまり起きない特徴なような気がしています。
全く同じ効果のメソッドであれば、
どちらを使ってもいいのですが、
微妙に効果が違うメソッドの場合は、
条件によって使い分ける必要があるのが、
Rubyですね。
添え字に範囲を指定する
Rubyの便利なところとして、
範囲指定というものがあります。
まあ、他のプログラミング言語にもありますが、
Rubyは結構直感的に範囲指定できる印象があります。
通常、arr[1]みたいな感じで、
要素を取得すると思いますが、
Rubyは、配列の範囲を指定して取得することがしやすいです。
irb(main):010:0> arr = [2,3,4,5,10]
=> [2, 3, 4, 5, 10]
irb(main):011:0> arr
=> [2, 3, 4, 5, 10]
irb(main):012:0> arr[1..3]
=> [3, 4, 5]
arr[1..3]みたいにすると、
1から3の要素を取得してくれるので、
配列の順番でいうと、2番目から4番目の要素を取得できます。
ちなみに、1..3自体は、
範囲オブジェクトというもので、
配列特有の記法ではなく、
オブジェクトです。
これがRubyの面白いところですね。
気軽に範囲をオブジェクトにして、
渡すことができるのは、
Ruby以外の言語ではそこまでし易いわけでは無い気がするので、
これはRubyの良いところだと思います。
https://docs.ruby-lang.org/ja/latest/class/Range.html
ループ処理
さて、次はループ処理について、
解説します。
Rubyにはループ処理をするための、
記法が実にたくさんあります。
その中でもブロックeachなどは、
一番良く使います。
ブロックについては、奥が深いかつ、
Rubyの根幹にかかわる、
大事な文法概念なので、
下記の記事でRubyのブロックについて、
詳細解説しています。
本記事でもブロックについて、
軽く触れはしますが、
詳細は下記の記事に譲ります。
-
参考【Rubyブロック】yieldとProcオブジェクト
Rubyの文法の中で、やはり避けては通れないぐらい重要な概念として、ブロックがあります。 このブロックは、ものすごくよく使いますし、重要な概念ですが、その奥深いところは、結構難しいかなと思います。 本 ...
続きを見る
また、ブロック以外にも、
ループ関連は色々あるのでとりあげます。
ブロック
ブロックは、初めてRubyを学習したときに、
一番良くわからない概念の一つだと思います。
そして、Rubyでは必ず使う重要なものでもあるので、
Rubyの習熟度はブロックへの理解度といっても過言ではないぐらい、
Rubyを使うなら重要です。
ブロックは奥が深いので、
本記事では本当にかいつまんで簡単な解説にとどめます。
( 詳細記事はこちら )
とりあえず、ブロックは一旦
do~endとか{}とかの塊と思っていただいて大丈夫です。
本記事は、Ruby基礎編なので、
ブロックは、上記のものと一旦思っておいてください。
説明するより、コードを見たほうがはやいと思うので、
コードを載せます。
[1,2,3].each do |f|
puts f
end
こんな感じです。
{}で短く書くこともできます。
[1,2,3].each { |f| puts f }
上記を見ると、確かにdo~endの塊があり、
中にputs fがありますね。
2つ目の省略記法だと、
{}の中に、同じくputs fがありますね。
平たく言えば、
ブロックとはこの部分のことです。
ブロックは多くの場合、
eachメソッドと一緒に使われることが多いです。
以下詳細に解説します。
each
ブロックと一緒に使われることが多いものとして、
このeachメソッドがあります。
Array#each (Ruby 3.2 リファレンスマニュアル) (ruby-lang.org)
各要素に対してブロックを評価します。
ブロックが与えられなかった場合は、自身と each から生成した Enumerator オブジェクトを返します。
Ruby 3.2 リファレンスマニュアル
上記のコードの場合、
各要素とは、[1,2,3]で
ブロックは、do |f| puts f endの部分を指します。
すなわち、eachとは、
要素を取り出してブロックにわたすメソッドです。
これが事実上、他の言語でいう、
for文のような機能に相当しているという感じです。
下記の記事で、
Rubyの特徴として
「すべてがオブジェクトである」ということを
紹介しました。
-
参考【Rubyすべてがオブジェクト】変数と真偽値/論理演算子/コメント
本記事は、Rubyの基礎文法である、変数や真偽値、論理演算子に触れると同時に、「すべてがオブジェクト」というRubyの特徴的な思想についても解説します。 この思想は、Rubyの文法の根幹になっているの ...
続きを見る
このeachとブロックの機能も、
ある意味この特徴と接点があるように思えます。
他の言語でいう、for文とかは、
オブジェクトに対して呼び出すのではなく、
関数のような感じで、引数にオブジェクトに相当するものなどを
持っていくという構文ですが、
Rubyの場合、すべてがオブジェクトなので、
このオブジェクトからメソッドを呼び出すというかたちで、
eachとブロックを使った構文のほうが、この思想に近い書き方にあたるでしょう。
もちろん、これは単なる私の感想なので、
こういう理由でブロックやeachが尊ばれているわけでもないとは思いますが、
他のプログラミング言語ではあまり登場しないブロックやeachのような記法も、
「すべてがオブジェクトである」起点から考えたら、結構素直な記法に
見えてこないでしょうか?
map
each以外にもブロックと
一緒に使われることが多いメソッドとして
mapもあります。
mapは、各要素にブロックを適用し、
新しい配列を作成するメソッドです。
Enumerable#collect (Ruby 3.2 リファレンスマニュアル) (ruby-lang.org)
[1,2,3].map { |f| puts f * 10 }
p [1,2,3].map { |f| f * 10 }
これの実行結果は、
10, 20, 30と10倍したものが出力されます。
これもよく使うので、
ぜひとも使いこなせるようになりたいですね。
&:メソッド
上記のmapメソッドのように
ブロックを使うものの場合、
簡潔に書く記法があります。
p [1,2,3].map(&:odd?) # [true, false, true]と出力される
# p [1,2,3].map { |f| f.odd? }の省略形
ブロックパラメーター(|f|)が一つだけで、
メソッド(odd?)に引数がなくて、
ブロック内でブロックパラメーター(|f|)に対してメソッド(odd?)を1回呼び出す以外の処理がないとき、
&:メソッド名という省略形を使用できます。
これは、最初にRubyを勉強しているときに遭遇すると
「なんじゃこりゃ?」ってなる記法の定番ですね。
Rubyには、省略記法がたくさんあるので、
これのように省略が原型をとどめていなかったりするものもあるので、
混乱しないよう気をつけたいですね。
for
ブロック周りのループ処理に触れたので、
お次は、forに移りたいと思います。
他のプログラミング言語ではめちゃくちゃよく使う、
あのforです。
しかし、カルチャーショック。
Rubyではまず使わないです。
記法としては、
for i in (1..10)
puts i
end
$ ruby index.rb
1
2
3
4
5
6
7
8
9
10
こんな感じですね。
Pythonそっくりの記法ですね。
簡潔でわかりやすくて美しいなと
個人的には感じます。
ただ、先述のように、
Rubyにおいて、forはほとんど使われません。
Rubyにおいて、なぜforが使われないのか、
詳しい理由はわかりませんが、
Rubyではeachやブロックを使った繰り返し処理がデファクトで、
forを使うシーンがほぼありませんし使う必要性もあまりありません。
あと、Rubyでforを使うと、
「こいつeachとかブロック知らないんか?」みたいな顔されるので、
よほどの理由がない限りforを使うのは避けた方が無難だと思います。
なんというか慣習なんですかね。
PythonからRubyに来た人は、
forの文法がめちゃくちゃ似ているので、
forを使いたくなると思いますけど、
ぐっとこらえてeachとブロック覚えましょう。
すべてがオブジェクトの思想のRubyにとっては、
forは、一ミリもその思想を表現していないので、
Rubyらしい記法という点では、
ブロックとeachになってくるというのは、
確かにという感じですね。
while
お次は、whileです。
これは、他のプログラミング言語にもある、
お決まりのwhileでして、
Rubyも比較的似たような感じです。
a = 5
while a < 10
puts '10以下です'
a += 1
end
ruby index.rb
10以下です
10以下です
10以下です
10以下です
10以下です
whileはforよりは、
使うシーンはあるような気がしますが、
それでもあまり使っているところを見たことがありません。
Rubyは、eachとブロックで、
ループ処理は、大抵こなせてしまうんですよね。
until
until文は、ちょっと珍しいかもしれません。
until文は、whileの逆で、
条件が偽である限り繰り返す処理です。
こういった構文がない言語もあると思うので、
やや珍しいかもしれないですね。
Rubyの場合、ifの逆のような
unlessのような構文があったりと、
逆の文法があったりするので、
どちらを使うか迷いがちですが、
基本は条件が真のときに実行する、
ifとかwhileを優先して使うでいいと思います。
(whileに関しては、eachとブロックの方を優先して使うべきだと思いますが)
untilとかが乱立していると、
混乱しがちな気がります。
a = 1
until a > 10
puts '10以下です'
a += 1
end
Rubyでは、ブロックやeachが主流なのと、
untilよりもwhileのほうがいいのと、
という二重の理由で、
untilを使うのはよほどのことが無い限りほとんど無い気がします。
それにuntilは、unlessよりも、
脳内の切り替えリソースを消費する気がするので、
読みやすいコードとしては微妙な気がします。
times
最後にtimesです。
これは、意外と使う時あります
timesは、指定回数処理を繰り返したいときとかに使いやすいです。
記法としては、
10.times do |f|
puts f
end
$ ruby index.rb
0
1
2
3
4
5
6
7
8
9
のような感じですね。
Railsのタスクとかで、
timesを使うと楽な場面があったりするので、
for/while/untilとかよりは、確実に
使用頻度が高いと思います。
timesは、範囲オブジェクトとか
数値オブジェクトに対して呼び出すので、
本質的にはeachと変わらないので、
「すべてがオブジェクト」の思想には
適っています。
それでも、使用頻度としては
eachやmapのほうが多いですが。
timesは、たまに使う場面があったりしますが、
あまりスマートな書き方とはみなされなかったりするので、
乱発は控えておいた方が無難です。
スコープ
ブロックや変数とかを学ぶと、
必ずつきまとう概念にスコープというものがあります。
これは、ブロックやfor文の中などで定義した変数が、
その外でも有効なのかといった、
変数の有効範囲のことをいいます。
スコープは、プログラミング言語によって微妙に異なるので、
ある意味スコープの違いを知ることがプログラミング言語の学習みたいな
側面もあったりするぐらい、スコープを知っておくことは重要です。
まず、ブロックの場合、
[1,2,3].each do |f|
value = 'hello'
puts f
puts value
end
puts value
$ ruby index.rb
1
hello
2
hello
3
hello
Traceback (most recent call last):
index.rb:6:in `<main>': undefined local variable or method `value' for main:Object (NameError)
ブロックの場合、
ブロック内で定義した変数は、
ブロックの外では参照できません。
これに対して、for文とかの場合、
for i in (1..3)
value = 'hello'
puts value
puts i
end
puts 'この下はforの外'
puts value
$ ruby index.rb
hello
1
hello
2
hello
3
この下はforの外
hello
なんと、forの場合は、
forの中で定義した変数valueが、
外でも参照できてしまいました。
スコープが外にまで広がっていますね。
この特徴は、あまり見かけないので、
かなり直感に反すると思います。
一般的にスコープは、内側から外を参照することはできますが、
外から内側を参照できるケース(Rubyのfor)はほとんど無い気がします。
こうなると、内と外で変数が衝突したりと、
意図しない不具合につながるかもしれません。
forを使うことがあまりないのも、
もしかするとこの挙動が関係しているかもしれないですね。
結論、forを使うことはあまりないと思うので、
そこまで重要ではないかもしれませんが、
eachとforには、一応こうした違いがあるということを
念頭に入れておくといいかもしれません。