大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【無職に転生 ~ 就職するまで毎日ブログ出す_18】【Ruby】Range

はじめに

こんにちは、大ちゃんの駆け出し技術ブログです。

タイトルにあるとおり【無職に転生 ~ 就職するまで毎日ブログ出す】というチャレンジをしています!!!!昨日までは就活するまで本気出すでしたが、これだとまるで就活後は頑張らないのかと思われてしまいそうで、、、大人気アニメのタイトルのまるパクリチャレンジです。

https://i.gyazo.com/3a02b7aae4e5e538130d4bb199b55dc7.png

RailsやらRubyやらSQLやらその他Webの知識やらが色々と抜け落ちているのを感じており、知識の定着のためにもアウトプットする機会を増やすためです。加えて、退職して文字通り無職に転生しましてプロニートになり、毎日時間に余裕ができたので引き締めるためにも毎日投稿を思い至りました!

【投稿内容】

  • SQLの難しい処理 (副問合せ、JOINとか複雑な処理が書けない)
  • Rails全般 (純粋に必要な知識が多すぎる、網羅的な理解が足りない)
  • Rubyのあまり使わないメソッドや記述方法 (あまり重要ではないけど特に)
  • Web知識全般 (クッキーやら、セッションやらなんとなくで理解しているものの自分の言葉で説明できない)
  • 書籍 (スタートアップ企業に勤めるので、自分が会社に与える影響やパフォーマンスを高めるためビジネス書を読んでいきます。)

本日やること

本日はRubyのRangeクラスについて書いていきます。Rangeクラスはあまりピンとこない方も多いかもしれませんが、Rubyを触っていれば時々何気なく使っています。

class Range

範囲オブジェクトのクラス。範囲オブジェクトは文字どおり何らかの意味での範囲を表します。数の範囲はもちろん、日付の範囲や、「"a" から "z" まで」といった文字列の範囲を表すこともできます。

要は書きみたいな記述。

(1..3)
=> 1..3

これが実はクラスだったんですね。今まで配列の省略記法と思っていましたが実は別クラスのインスタンスでした。

(1..3).class
=> Range

Rangeインスタンスの作り方

範囲オブジェクトを作る書き方としては一般的には上述したような書き方を使います。例えば、1 ~ 5の数の範囲オブジェクトを作る場合は以下のように記述します。

(1..5)
=> 1..5

このように数..数を()で囲むことで始端から終端までの範囲オブジェクトを生成できます。上記と同じ書き方として、Rangeのnewメソッドを使用することもできます。一般的なインスタンスの生成方法ですね。第一引数に始端、第二引数に終端を入れることで始端から終端までの範囲オブジェクトを作ることができます。

Range.new(1, 5)
=> 1..5

また、終端を含めないやり方として、...で始端と終端を繋げる方法があります。これにより終端の一つ手前の数を終端とした範囲オブジェクトを作ることができます。

(1...5)
=> 1...5

上記で5が含まれていないことを確認してみます。eachメソッドを使うことができるのでeachで範囲オブジェクトの中の各値を出力してみます。

(1...5).each {|i| p i}
# 出力
1
2
3
4

このように5を含まない1から4までの値が出力されます。上記の方法はRangeクラスでは行うことはできません。

また、アルファベット順で範囲オブジェクトを作ることもできます。使う機会は少ないかなと思いますが、、。

Range.new("a", "e").each {|s| p s}
"a"
"b"
"c"
"d"
"e"
=> "a".."e"

始端と終端をnilにすることもできます。ただ、使い所はあるのかわかりません。それぞれ、「終端を持たない範囲オブジェクト」、「始端を持たない範囲オブジェクト」と称されるそうです。

rubyのバージョンによってはできない場合もありますので注意してください。

Range.new(1, nil) # ruby2.6より
=> 1..
Range.new(nil, 5)  # ruby 2.7より
=> ..5
Range.new(nil, nil) 
=> nil..nil

範囲オブジェクトはイミュータブル

範囲オブジェクトはruby3.0.0以降イミュータブルなオブジェクトです。つまり、破壊的メソッドなどが使えず変更されることがないということです。理由としてはfreezeメソッドが初期化時に実行されるらしいです。

freezeメソッド

オブジェクトを凍結(内容の変更を禁止)します。 凍結されたオブジェクトの変更は例外 FrozenError を発生させます。いったん凍結されたオブジェクトを元に戻す方法はありません。 凍結されるのはオブジェクトであり、変数ではありません。代入などで変数の指すオブジェクトが変化してしまうことは freeze では防げません。 freeze が防ぐのは、 `破壊的な操作' と呼ばれるもの一般です。変数への参照自体を凍結したい場合は、グローバル変数なら Kernel.#trace_var が使えます。

Object#freeze (Ruby 3.0.0 リファレンスマニュアル)

a1 = "foo".freeze
a1 = "bar"
p a1 #=> "bar" # 変数の代入はできる

a2 = "foo".freeze
a2.replace("bar") # can't modify frozen String (FrozenError)

オブジェクトが凍結されているかどうかはfrozen?メソッドで確認できるので範囲オブジェクトに対して実行してみます。

Range.new(1, 5).frozen?
=> true

インスタンスメソッド

Rangeクラスのインスタンスメソッドは以下のようになります。

https://i.gyazo.com/97bcb04482683389d585df7443bc8001.png

他のクラスに比べるとかなり少ないですね。これに加えてEnumerableモジュールがincludeされています。Enumerableモジュールについては以前こちらの記事で紹介しました。

【無職に転生 ~ 就職するまで毎日ブログ出す_13】【Ruby】Enumerable - 大ちゃんの駆け出し技術ブログ

インスタンスメソッドからいくつか使えそうなメソッドを紹介します。

cover?(obj)

引数で指定したオブジェクトがレシーバーの範囲オブジェクトの範囲に含まれているかどうかを真偽値で判定するメソッドです。

(1..5).cover?(5)
=> true
(1...5).cover?(5)
=> false

実はinclude?メソッドがRangeクラスに定義されているのですが、こちらもほぼ同じ挙動を見せます。

(1..5).include?(5)
=> true
(1...5).include?(5)
=> false

明確な違いとしてはcover?の方には引数に範囲オブジェクトを指定することができます。

(1..5).cover?(1..5)
=> true
(1...5).cover?(1..5)
=> false

include?にも範囲オブジェクトを引数に指定知ることはできるのですが、どんな引数でもfalseを返してしまうので全く機能していません。

(1...5).include?(1..4)
=> false
(1...5).include?(1..2)
=> false

max

範囲オブジェクトの終端の値を取得します。

("a".."w").max
=> "w"
("a"..."w").max
=> "v"

また、引数に数値を指定することで、終端から引数の数値の数だけ範囲オブジェクトの値を取得します。返却値は配列となります。

("a"..."w").max(3)
=> ["v", "u", "t"]

min

maxの逆です。範囲オブジェクトの始端の値を取得します。

("a".."w").min
=> "a"

こちらも引数を指定することでその数だけ範囲オブジェクトのしたんから始まる要素を取得します。

("a".."w").min(10)
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]

ちなみに引数が範囲オブジェクトの要素を超えた場合は全要素を配列として返却します。エラーにはなりません。

("a".."w").min(100)
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w"]
("a".."w").max(100)
=> ["w", "v", "u", "t", "s", "r", "q", "p", "o", "n", "m", "l", "k", "j", "i", "h", "g", "f", "e", "d", "c", "b", "a"]

minmax

始端と終端を配列として取得します。

("a".."w").minmax
=> ["a", "w"]

step

stepメソッドはIntegerクラスで使われることが多い気がします。

2.step(5){|n| p n}
# 出力
2
3
4
5

上記の場合はレシーバーは数値で引数も数値です。そしてブロックを使うことでレシーバーの数から引数の数まで出力します。

Rangeクラスのstepメソッドの場合、始端から引数の数だけ飛ばした数を次々に出力するメソッドです。

(0..100).step(10) {|i| puts i}
# 出力
0
10
20
30
40
50
60
70
80
90
100

to_a

範囲オブジェクトを配列に変えることができます。すごく使い勝手が良さそうです。Arrayクラスだけで1から100までの配列を作ろうとした場合、以下のようにmapメソッドを使うことで作成できます。

array = 100.times.map{|i| i + 1}
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51...

しかし上述したやり方ですと見栄えが少し悪い気がします。そこでRangeクラスとto_aメソッドを組み合わせればかなりシンプルに1から100までの配列を作ることができます。

array = (1..100).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51...