やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

組込ライブラリ(Range)

 範囲オブジェクト。

成果物

情報源

Range

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

 文字や日付まで範囲にできたのか。知らなかった。

作り方

範囲オブジェクトは、Range.new を用いるほか、範囲演算子..' または...')を用いた 演算子式/範囲式 で生成できます。いずれの方法でも始端と終端を与えます。

範囲オブジェクトの例

Range.new(1, 5) # 1 以上 5 以下
1..5            # 同上
1...5           # 1 以上 5 未満

この例で分かるように、範囲オブジェクトは終端を含む範囲も含まない範囲も表せます。

 「この例」というか「以下の例」ね。

Ruby 2.6.0 からは、終端に nil を与えることで「終端を持たない範囲オブジェクト」を作ることができるようになりました。

終端を持たない範囲オブジェクト

p Range.new(1, nil) # 1 以上(上限無し)を表す
p(1..nil)           # 同上
p(1..)              # 同上(略した書き方)

 普通は省略すればいいと思う。省略せず、わざわざnilを書くことで「書き忘れではない」ことを明示できる効果があるのだろう。

また、Ruby 2.7.0 では始端に nil を与えることで「始端を持たない範囲オブジェクト」を作ることもできるようになりました。

始端を持たない範囲オブジェクト

p Range.new(nil, 5) # 5 以下(下限無し)を表す
p(nil..5)           # 同上
p(..5)              # 同上(略した書き方)

 やはりnilがあると可読性が下がる。

始端も終端も持たない範囲オブジェクトは「全範囲」を表します。

始端も終端も持たない範囲オブジェクト

# 以下はすべて同じ範囲
p Range.new(nil, nil) # => nil..nil
p(nil..nil)           # => nil..nil
p(..nil)              # => nil..nil
p(nil..)              # => nil..nil

範囲式で両端を略した書き方はできません。

p(..)  # => SyntaxError
p(...) # Ruby 2.7 で導入されたメソッド引数の forward として解釈されてしまう

 なんだよ全範囲って。Range.newの引数なしでも以下エラーになり全範囲にならない。えー。

p Range.new # wrong number of arguments (given 0, expected 2..3) (ArgumentError)

 ちなみに全範囲だと初期値がいくつになるのか試そうと思ったがエラーになった。

(nil..nil).each{|i| p i} # can't iterate from NilClass (TypeError)

 全範囲って何なの? 範囲なしじゃないの? どうやって使うの?

機能

範囲オブジェクトは範囲を表しているので、基本的な機能として「ある値がその範囲に含まれるか否かを判定する」ということがあります。

値が範囲に含まれるかどうかを判定

p (1..5).cover?(6)  # => false
p (1..5).cover?(5)  # => true
p (1...5).cover?(5) # => false

 便利だね。

Range#cover? メソッドでの判定には演算子 <=> が使われます。

 ロケット演算子

当然、始端と終端は <=> メソッドで比較可能である(nil 以外を返す)必要があります。

 同じなら0、左辺が大きければ1、左辺が小さければ-1を返すんだっけ?

範囲オブジェクトのもう一つの基本的機能は繰り返しの範囲を表すことです。

繰り返しの範囲を範囲オブジェクトで表す

(3..5).each{ |i| p i }
# => 3
#    4
#    5

(3...5).each{ |i| p i }
# => 3
#    4

 .....は終端値が違うみたい。ややこしいから..だけ覚えればいいんじゃないかな?

繰り返しの範囲を表す範囲オブジェクトは、始端が「次の値」を返す succ メソッドを持たなければなりません。

 nextでもいいのかな? たしか同じ効果だったはずだが。

Range クラスには Enumerable が include してあるので,Range#each に基づき、Enumerable モジュールが提供する多様なメソッドを使うことができます。

 ふーん。Enumerableってモジュールなんだ。

破壊的な変更

Ruby の Range クラスは immutable です。つまり、オブジェクト自体を破壊的に変更することはできません。ですので、一度生成された Range のオブジェクトの指し示す範囲は決して変更することはできません。

range = 1..10
range.first     # => 1
range.first = 1 # => NoMethodError

また、Ruby 3.0.0 からすべての Range オブジェクトは freeze されるようになりました。

p (1..10).frozen?
# => true
p Range.new(1, 10).frozen?
# => true

 Object#freezeは破壊的な変更を禁止する。これは二度と元に戻せない。つまりイミュータブルになる。

メンバ抜粋

特異メソッド

new

インスタンスメソッド

% == === begin bsearch cover? each end entries eql? exclude_end? first hash include? inspect last max member? min minmax size step to_a to_s

begin/first/min, end/last/max

 開始値、終端値を返す。

概要 メソッド
始値 begin/first/min
終端値 end/last/max
r = (1..9)
p r.begin   # 1
p r.first   # 1
p r.min     # 1
p r.end     # 9
p r.last    # 9
p r.max     # 9
p r.first 3 # [1,2,3]
p r.min 3   # [1,2,3]
p r.last 3  # [7,8,9]
p r.max 3   # [9,8,7]

step

p (1..9).step(2) {|i| p i} # 1 3 5 7 9

size

p (1..9).size # 9
p (1..9).step(2).size # 5

cover?

 引数が範囲内なら真を返す。

p (1..9).cover? # 9

 引数が範囲内であるかどうか判定するときはinclude?よりcover?を使用したほうがよい。

メソッド 概要
cover? <=>により範囲内か判定する
include? 引数が範囲内に含まれるとき真を返す

 cover?include?は引数が数値のとき同じ結果を返す。ただし文字列や日付型のときは違う。

(1.1..2.3).include?(1.0)    # => false
(1.1..2.3).include?(1.1)    # => true
(1.1..2.3).include?(1.555)  # => true
(1.1..2.3).cover?(1.0)      # => false
(1.1..2.3).cover?(1.1)      # => true
(1.1..2.3).cover?(1.555)    # => true
('b'..'d').include?('d')    # => true
('b'..'d').include?('ba')   # => false
('b'..'d').cover?('d')      # => true
('b'..'d').cover?('ba')     # => true
require 'date'
(Date.today - 365 .. Date.today + 365).include?(Date.today)    #=> true
(Date.today - 365 .. Date.today + 365).include?(DateTime.now)  #=> false
(Date.today - 365 .. Date.today + 365).cover?(Date.today)      #=> true
(Date.today - 365 .. Date.today + 365).cover?(DateTime.now)    #=> true

 cover?はロケット演算子で判定するとか言っているわりに、真偽値で返してる。0,1,-1の3値で返すんじゃないのか。じゃあなんでロケット演算子で判定するって説明をしたんだ? 説明の意図がよくわからん。たぶん実装がそうなっているのだろう。細かい違いはコード読めってことなんだろうな。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux