class Range

[edit]

要約

範囲オブジェクトのクラス。範囲オブジェクトは文字どおり何らかの意味での範囲を表します。数の範囲はもちろん、日付の範囲や、「"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..)              # 同上(略した書き方)

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

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

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

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

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

# 以下はすべて同じ範囲
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 として解釈されてしまう

機能

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

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

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

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

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

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

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

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

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

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

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

破壊的な変更

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


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

目次

特異メソッド
インスタンスメソッド

継承しているメソッド

Enumerableから継承しているメソッド

特異メソッド

new(first, last, exclude_end = false) -> Range[permalink][rdoc][edit]

first から last までの範囲オブジェクトを生成して返します。

exclude_end が真ならば終端を含まない範囲オブジェクトを生成します。exclude_end 省略時には終端を含みます。

[PARAM] first:
最初のオブジェクト
[PARAM] last:
最後のオブジェクト
[PARAM] exclude_end:
真をセットした場合終端を含まない範囲オブジェクトを生成します
[EXCEPTION] ArgumentError:
first <=> last が nil の場合に発生します
例: 整数の範囲オブジェクトの場合

Range.new(1, 10)       # => 1..10
Range.new(1, 10, true) # => 1...10
例: 日付オブジェクトの範囲オブジェクトの場合

require 'date'
Range.new(Date.today, Date.today >> 1).each {|d| puts d }
# => 2017-09-16
#    2017-09-17
#    ...
#    2017-10-16
例: IPアドレスの範囲オブジェクトの場合

require 'ipaddr'
Range.new(IPAddr.new("192.0.2.1"), IPAddr.new("192.0.2.3")).each {|ip| puts ip}
# => 192.0.2.1
#    192.0.2.2
#    192.0.2.3
例: 自作のオブジェクトの場合

MyInteger = Struct.new(:value) do
  def succ
    self.class.new(value + 1)
  end

  def <=>(other)
    value <=> other.value
  end

  def to_s
    value.to_s
  end
end
Range.new(MyInteger.new(1), MyInteger.new(3)).each {|i| puts i }
# => 1
#    2
#    3

インスタンスメソッド

step(s = 1) {|item| ... } -> self[permalink][rdoc][edit]
step(s = 1) -> Enumerator
step(s = 1) -> Enumerator::ArithmeticSequence
self % s -> Enumerator
self % s -> Enumerator::ArithmeticSequence

範囲内の要素を s おきに繰り返します。

[PARAM] s:
各ステップの大きさを数値で指定します。負の数を指定することもできます。
[RETURN]
ブロックを指定した時は self を返します。
[RETURN]
ブロックを指定しなかった時かつ数値の Range の時は Enumerator::ArithmeticSequence を返します。
[RETURN]
ブロックを指定しなかったその他の Range の時は Enumerator を返します。(例: String の Range)


("a" .. "f").step(2) {|v| p v}
# => "a"
#    "c"
#    "e"
self == other -> bool[permalink][rdoc][edit]

指定された other が Range クラスのインスタンスであり、始端と終端が == メソッドで比較して等しく、Range#exclude_end? が同じ場合に true を返します。そうでない場合に false を返します。

[PARAM] other:
自身と比較したいオブジェクトを指定します。


p (1..2) == (1..2)               # => true
p (1..2) == (1...2)              # => false
p (1..2) == Range.new(1.0, 2.0)  # => true
self === obj -> bool[permalink][rdoc][edit]

始端と終端の中に obj があるとき、true を返します。そうでないとき、false を返します。

Range#=== は主に case 式での比較に用いられます。



p (0...50) === 79  #=> false
p (60...80) === 79 #=> true

case 79
when  0...60  then  puts "low"
when 60...80  then  puts "medium" # => medium
when 80..100  then  puts "high"
end

2.5 以前は、単純に Range#include? メソッドを内部で呼んでいました。

しかし、2.6 以降では、(文字列を除いて) Range#cover? と同様の処理をするように切り替わりました。

ただし、=== は、Range#cover? のように Range オブジェクトを引数にはとる設計はありません。



require 'date'
p (Date.today - 100...Date.today + 100).include?(DateTime.now)  #=> false
p (Date.today - 100...Date.today + 100).cover?(DateTime.now)    #=> true
p (Date.today - 100...Date.today + 100) ===  DateTime.now       #=> true
# 2.5 以前は、=== は、include? と同じく比較できず false を返していました。

2.7 以降の === は、文字列も Range#cover? と同様の処理をするようになりました。



p ('a'..'z').include? 'at'  #=> false
p ('a'..'z').cover? 'at'    #=> true
p ('a'..'z') === 'at'       #=> true
# 2.6 以前は、=== は、include? と同じく比較できず false を返していました。

[SEE_ALSO] 制御構造/case

[SEE_ALSO] Range#include?, Range#cover?

begin -> object[permalink][rdoc][edit]
first -> object

始端の要素を返します。始端を持たない範囲オブジェクトの場合、begin はnilを返しますが, first は例外 RangeError が発生します。



# 始端を持つ場合
p (1..5).begin # => 1
p (1..0).begin # => 1
p (1..5).first # => 1
p (1..0).first # => 1

# 始端を持たない場合
p (..5).begin #=> nil
p (..5).first #=> RangeError

[SEE_ALSO] Range#end

first(n) -> [object][permalink][rdoc][edit]

最初の n 要素を返します。範囲内に要素が含まれない場合は空の配列を返します。

[PARAM] n:
取得する要素数を整数で指定します。整数以外のオブジェクトを指定した場合は to_int メソッドによる暗黙の型変換を試みます。
[EXCEPTION] TypeError:
引数に整数以外の(暗黙の型変換が行えない)オブジェクトを指定した場合に発生します。
[EXCEPTION] ArgumentError:
n に負の数を指定した場合に発生します。


(10..20).first(3)  # => [10, 11, 12]

[SEE_ALSO] Range#last, [ruby-core:12697]

bsearch {|obj| ... } -> object | nil[permalink][rdoc][edit]
bsearch -> Enumerator

ブロックの評価結果で範囲内の各要素の大小判定を行い、条件を満たす値を二分探索(計算量は O(log n))で検索します。要素が見つからない場合は nil を返します。

本メソッドはブロックを評価した結果により以下のいずれかのモードで動作します。

  • find-minimum モード
  • find-any モード

find-minimum モード(特に理由がない限りはこのモードを使う方がいいでしょう)では、条件判定の結果を以下のようにする必要があります。

  • 求める値がブロックパラメータの値か前の要素の場合: true を返す
  • 求める値がブロックパラメータより後の要素の場合: false を返す

ブロックの評価結果が true になる最初の要素を返すか、nil を返します。



ary = [0, 4, 7, 10, 12]
(0...ary.size).bsearch {|i| ary[i] >= 4 } # => 1
(0...ary.size).bsearch {|i| ary[i] >= 6 } # => 2
(0...ary.size).bsearch {|i| ary[i] >= 8 } # => 3
(0...ary.size).bsearch {|i| ary[i] >= 100 } # => nil

(0.0...Float::INFINITY).bsearch {|x| Math.log(x) >= 0 } # => 1.0

find-any モードは bsearch(3) のように動作します。ブロックは真偽値ではなく、以下のような数値を返す必要があります。求める値の範囲がx...y (x <= y)であるとします。また、ブロックパラメータの値を v とします。

  • ブロックパラメータの値が求める値の範囲よりも小さい(v < x)場合: 正の数を返す
  • ブロックパラメータの値が求める値の範囲に合致する(x <= v < y)場合: 0 を返す
  • ブロックパラメータの値が求める値の範囲よりも大きい(y <= v)場合: 負の数を返す

ブロックの評価結果が 0 になるいずれかの要素を返すか、nil を返します。



ary = [0, 100, 100, 100, 200]
(0..4).bsearch {|i| 100 - ary[i] } # => 1, 2 or 3
(0..4).bsearch {|i| 300 - ary[i] } # => nil
(0..4).bsearch {|i|  50 - ary[i] } # => nil

上記の 2 つのモードを混在して使用しないでください(ブロックの評価結果は常に true/false、数値のいずれかを一貫して返すようにしてください)。また、二分探索の各イテレーションで値がどのような順序で選ばれるかは未規定です。

ブロックが与えられなかった場合は、 Enumerator のインスタンスを返します。

[EXCEPTION] TypeError:
ブロックの評価結果が true、false、nil、数値以外であった場合に発生します。

[SEE_ALSO] Array#bsearch

cover?(obj) -> bool[permalink][rdoc][edit]

obj が範囲内に含まれている時に true を返します。

Range#include? と異なり <=> メソッドによる演算により範囲内かどうかを判定します。 Range#include? は原則として離散値を扱い、 Range#cover? は連続値を扱います。(数値については、例外として Range#include? も連続的に扱います。)

Range#exclude_end?がfalseなら「begin <= obj <= end」を、 trueなら「begin <= obj < end」を意味します。

[PARAM] obj:
比較対象のオブジェクトを指定します。
数値は連続的に扱われているため、 include? / cover? が同じ結果を返す

(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
String の例

('b'..'d').include?('d')    # => true
('b'..'d').include?('ba')   # => false
('b'..'d').cover?('d')      # => true
('b'..'d').cover?('ba')     # => true
Date, DateTime の例

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?(range) -> bool[permalink][rdoc][edit]

2.6 以降の cover? は、Range#include?Range#=== と異なり、引数に Range オブジェクトを指定して比較できます。

引数が Range オブジェクトの場合、引数の範囲が self の範囲に含まれる時に true を返します。

[PARAM] range:
比較対象の Range クラスのインスタンスを指定します。
引数が Range の例

(1..5).cover?(2..3)     #=> true
(1..5).cover?(0..6)     #=> false
(1..5).cover?(1...6)    #=> true

「(a..b).cover?(c...d)」のように終端を含まない Range オブジェクトが引数に渡されており、「a <= c && b < d」を満たし、cが数値ではない(つまり引数の Range の終端を求めるために succ メソッドの呼び出しが必要な)場合、パフォーマンスの問題が起きる可能性があります。

パフォーマンス上の問題が起きる例

p ('aaaaa'..'zzzzy').cover?('aaaaa'...'zzzzz') # => true

[SEE_ALSO] Range#include?, Range#===

each {|item| ... } -> self[permalink][rdoc][edit]
each -> Enumerator

範囲内の要素に対して繰り返します。

Range#each は各要素の succ メソッドを使用してイテレーションするようになりました。

[EXCEPTION] TypeError:
succ メソッドを持たないクラスの範囲オブジェクトに対してこのメソッドを呼んだ場合に発生します。


(10..15).each {|n| print n, ' ' }
# prints: 10 11 12 13 14 15

(2.5..5).each {|n| print n, ' ' }
# raises: TypeError: can't iterate from Float
end -> object[permalink][rdoc][edit]
last -> object

終端の要素を返します。範囲オブジェクトが終端を含むかどうかは関係ありません。



(10..20).last      # => 20
(10...20).last     # => 20

[SEE_ALSO] Range#begin

last(n) -> [object][permalink][rdoc][edit]

最後の n 要素を返します。範囲内に要素が含まれない場合は空の配列を返します。

[PARAM] n:
取得する要素数を整数で指定します。整数以外のオブジェクトを指定した場合は to_int メソッドによる暗黙の型変換を試みます。
[EXCEPTION] TypeError:
引数に整数以外の(暗黙の型変換が行えない)オブジェクトを指定した場合に発生します。
[EXCEPTION] ArgumentError:
n に負の数を指定した場合に発生します。

[注意] 引数を省略して実行した場合は、終端を含むかどうか (Range#exclude_end? の戻り値)に関わらず終端の要素を返す事に注意してください。



(10..20).last(3)   # => [18, 19, 20]
(10...20).last(3)  # => [17, 18, 19]

[SEE_ALSO] Range#first

to_a -> Array[permalink][rdoc][edit]
entries -> Array

self を配列に変換します。

[EXCEPTION] RangeError:
終端のない Range オブジェクトを変換しようとしたときに発生します。


p (5..0).to_a      # => []
p (0..3).to_a      # => [0, 1, 2, 3]
p ('a'..'c').to_a  # => ["a", "b", "c"]
p (:a..:d).to_a  # => [:a, :b, :c, :d]

require 'date'
p (Date.new(1965, 4, 14) .. Date.new(1965, 4, 14)).to_a # => [#<Date: 1965-04-14 ((2438865j,0s,0n),+0s,2299161j)>]

(1..).to_a   # RangeError: cannot convert endless range to an array
eql?(other) -> bool[permalink][rdoc][edit]

指定された other が Range クラスのインスタンスであり、始端と終端が eql? メソッドで比較して等しく、Range#exclude_end? が同じ場合に true を返します。そうでない場合に false を返します。

[PARAM] other:
自身と比較したいオブジェクトを指定します。


p (1..2).eql?(1..2)                 # => true
p (1..2).eql?(1...2)                # => false
p (1..2).eql?(Range.new(1.0, 2.0))  # => false
exclude_end? -> bool[permalink][rdoc][edit]

範囲オブジェクトが終端を含まないとき真を返します。



(1..5).exclude_end?     # => false
(1...5).exclude_end?    # => true
hash -> Integer[permalink][rdoc][edit]

始端と終端のハッシュ値と Range#exclude_end? の値からハッシュ値を計算して整数として返します。



p (1..2).hash    # => 5646
p (1...2).hash   # => 16782863
include?(obj) -> bool[permalink][rdoc][edit]
member?(obj) -> bool

obj が範囲内に含まれている時に true を返します。そうでない場合は、false を返します。

<=> メソッドによる演算により範囲内かどうかを判定するには Range#cover? を使用してください。

始端・終端・引数が数値であれば、 Range#cover? と同様の動きをします。

[PARAM] obj:
比較対象のオブジェクトを指定します。


p ("a" .. "c").include?("b")  # => true
p ("a" .. "c").include?("B")  # => false
p ("a" .. "c").include?("ba") # => false
p ("a" .. "c").cover?("ba")   # => true

p (1 .. 3).include?(1.5) # => true

[SEE_ALSO] 制御構造/case

[SEE_ALSO] Range#cover?, Range#===

inspect -> String[permalink][rdoc][edit]

self を文字列に変換します(始端と終端のオブジェクトは #inspect メソッドで文字列に変換されます)。

[SEE_ALSO] Range#to_s



(1..5).inspect      # => "1..5"
("1".."5").inspect  # => "\"1\"..\"5\""
max -> object | nil[permalink][rdoc][edit]
max(n) -> [object]

範囲内の最大の値、もしくは最大の n 要素が降順で入った配列を返します。

[PARAM] n:
取得する要素数。


(1..5).max     # => 5


(1..5).max(3)  # => [5, 4, 3]

始端が終端より大きい場合、もしくは、終端を含まない範囲オブジェクトの始端が終端と等しい場合は、引数を指定しない形式では nil を返します。引数を指定する形式では、空の配列を返します。



(2..1).max     # => nil
(1...1).max    # => nil


(2..1).max(3)  # => []
(1...1).max(3) # => []
max {|a, b| ... } -> object | nil[permalink][rdoc][edit]
max(n) {|a, b| ... } -> [object]

ブロックの評価結果で範囲内の各要素の大小判定を行い、最大の要素、もしくは最大の n 要素を返します。引数を指定しない形式では、範囲内に要素が存在しなければ nil を返します。引数を指定する形式では、空の配列を返します。

ブロックの値は、a > b のとき正、 a == b のとき 0、a < b のとき負の整数を、期待しています。

[PARAM] n:
取得する要素数。
[EXCEPTION] TypeError:
ブロックが整数以外を返したときに発生します。

[SEE_ALSO] Range#last, Range#min, Enumerable#max



h = { 1 => "C", 2 => "Go", 3 => "Ruby" }
(1..3).max { |a, b| h[a].length <=> h[b].length }    # => 3


(1..3).max(2) { |a, b| h[a].length <=> h[b].length } # => [3, 2]
min -> object | nil[permalink][rdoc][edit]
min(n) -> [object]

範囲内の最小の値、もしくは最小の n 要素が昇順で入った配列を返します。

[PARAM] n:
取得する要素数。


(1..5).min    # => 1


(1..5).min(3) # => [1, 2, 3]

始端が終端より大きい場合、もしくは、終端を含まない範囲オブジェクトの始端が終端と等しい場合は、引数を指定しない形式では nil を返します。引数を指定する形式では、空の配列を返します。



(2..1).min     # => nil
(1...1).min    # => nil


(2..1).min(3)  # => []
(1...1).min(3) # => []
min {|a, b| ... } -> object | nil[permalink][rdoc][edit]
min(n) {|a, b| ... } -> [object]

ブロックの評価結果で範囲内の各要素の大小判定を行い、最小の要素、もしくは最小の n 要素を返します。引数を指定しない形式では、範囲内に要素が存在しなければ nil を返します。引数を指定する形式では、空の配列を返します。

ブロックの値は、a > b のとき正、a == b のとき 0、 a < b のとき負の整数を、期待しています。

[PARAM] n:
取得する要素数。
[EXCEPTION] TypeError:
ブロックが整数以外を返したときに発生します。

[SEE_ALSO] Range#first, Range#max, Enumerable#min



h = { 1 => "C", 2 => "Go", 3 => "Ruby" }
(1..3).min { |a, b| h[a].length <=> h[b].length }    # => 1


(1..3).min(2) { |a, b| h[a].length <=> h[b].length } # => [1, 2]
minmax -> [object, object][permalink][rdoc][edit]
minmax {|a, b| ... } -> [object, object]

範囲内の要素のうち、最小の要素と最大の要素を要素とするサイズ 2 の配列を返します。

一つ目の形式では、全要素が互いに <=> メソッドで比較できることを仮定しています。

二つ目の形式では、要素同士の比較をブロックを用いて行います。ブロックの値は、a > b のとき正、 a == b のとき 0、a < b のとき負の整数を、期待しています。



(1..3).minmax # => [1, 3]

h = { 1 => "C", 2 => "Go", 3 => "Ruby" }
(1..3).minmax { |a, b| h[a].length <=> h[b].length } # => [1, 3]
size -> Integer | Float::INFINITY | nil[permalink][rdoc][edit]

範囲内の要素数を返します。始端、終端のいずれかのオブジェクトが Numeric のサブクラスのオブジェクトではない場合には nil を返します。



(10..20).size    # => 11
("a".."z").size  # => nil
(-Float::INFINITY..Float::INFINITY).size # => Infinity
to_s -> String[permalink][rdoc][edit]

self を文字列に変換します(始端と終端のオブジェクトは #to_s メソッドで文字列に変換されます)。

[SEE_ALSO] Range#inspect



(1..5).to_s      # => "1..5"
("1".."5").to_s  # => "1..5"