Help us understand the problem. What is going on with this article?

Ruby Rangeクラスで範囲を扱うTips

More than 1 year has passed since last update.

概要

RubyのRangeクラスを雰囲気で使っていて、知識が曖昧だったので使い方の備忘録を残す。

前提

$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]

.. と ... の違い

p (1..5).to_a  # [1,2,3,4,5]
p (1...5).to_a # [1,2,3,4]

.. は、終端の数字を含める。
... は、終端の数字を含めない。

なんとなく点が多いほうが範囲が広いイメージもあって覚えられない。

Range.new

... と .. がどうしても覚えられないならRangeクラスのイニシャライザが使える。

p Range.new(1, 5, false).to_a # [1,2,3,4,5]
p Range.new(1, 5, true).to_a  # [1,2,3,4]

第三引数はexclude_end(終端を除外するのか)を指定するので、falseなら含む、trueなら除外する。
…どのみちtrueにしたらどうなるのか覚えてなきゃならないのであんまり意味がない。

日付でも使える

ちゃんと日にちベースで範囲が取れる。

require 'date'
range = (Date.parse('2018/02/26')..Date.parse('2018/03/02'))
range.each do |date|
  p date.to_s
end
"2018-02-26"
"2018-02-27"
"2018-02-28"
"2018-03-01"
"2018-03-02"

文字なんかもお手のもの

1文字はもちろん2文字以上でも可能。

p ('AA'..'BZ').to_a
["AA", "AB", "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", "AQ", "AR", "AS", "AT", "AU", "AV", "AW", "AX", "AY", "AZ", "BA", "BB", "BC", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BK", "BL", "BM", "BN", "BO", "BP", "BQ", "BR", "BS", "BT", "BU", "BV", "BW", "BX", "BY", "BZ"]

日本語もイケるけど、辞書順なので微妙。

p ('あ'..'こ').to_a
["あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お", "か", "が", "き", "ぎ", "く", "ぐ", "け", "げ", "こ"]

自作のクラスでもできる

自分で作ったクラスに、succと、<=>を実装すれば、自由にRangeを扱えるように出来る。

class Number
  attr_reader :value

  def initialize(number)
    @value = number
  end

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

  def succ
    Number.new(@value * 5)
  end
end

(Number.new(1)..Number.new(200)).each do |number|
  p number.value
end
1
5
25
125
  • Numberクラスは、数値でオブジェクトを生成でき、valueでそれを参照できる
  • Numberオブジェクト同士は、valueの大小で比較できる
  • succメソッドは、valueを5倍した数値で新たにNumberオブジェクトを作成して戻す

以上を元に、(Number.new(1)..Number.new(200)) でRangeを作成すると、valueが5倍ずつ増えるNumberオブジェクトのRangeになる。

ちなみにRangeは遅延評価になっているので、

def initialize(number)
  p 'initialize!!'
  @value = number
end
(Number.new(1)..Number.new(200))

とRangeを定義しただけの場合、先頭と終端で2回のみオブジェクトが生成される。to_aしたりeachしたりするタイミングで、範囲に含まれるオブジェクトも生成される。

"initialize!!"
"initialize!!"

Rangeに特定の値が含まれているか調べる①

Range#member? メソッドを使う。

p (1..100).member? 100   # true
p (1...100).member? 100  # false

Range#include? や Range#=== も同様。 === はどこか不自然に見える。

range = (1..100)
p range.include? 50   # true
p range.include? 200  # false
p range === 30        # true
p range === 0         # false

ちなみに先程のNumberクラスを使って以下のようにした場合、falseになる。見た感じRangeに含まれてそうだが、異なるオブジェクトを指してるのでfalseになる。

range = (Number.new(1)..Number.new(200))
p range.member? Number.new(1) # false

Rangeに特定の値が含まれているか調べる①

Range#cover? という似たようなメソッドがあるが、こちらはmember?とはRangeに含まれているかの判別方法が異なる。

まず、member? の挙動を見てみる。member? は、succメソッドに基いて生成されるRangeを対象に、それが存在するかを判別する。
そのため、下記の例では、'b'は存在するが、'b!'という文字列は存在しないので後者はfalseを戻す。

range = ('a'..'d')
p range.to_a   # ['a', 'b', 'c', 'd']

p range.member? 'b'  # true
p range.member? 'b!' # false

対してcover?の場合は、succメソッドに関係なく、<=>メソッドを用いて比較した場合に、先頭と終端の間にあるかを判別する。
下記のRangeは文字列のRangeのため、<=>メソッドは辞書順比較を行う。そのため、'b!'は辞書順で'a'以上'd'未満を満たすことからtrueを戻す。

range = ('a'..'d')
p range.to_a   # ['a', 'b', 'c', 'd']

p range.cover? 'b'  # true
p range.cover? 'b!' # true

Range内の特定の値を取る

Range#firstやRange#endを使う。

range = (1..100)
p range.first    # 1
p range.first(3) # [1,2,3]
p range.last     # 100
p range.last(3)  # [98,99,100]

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした