Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

RubyのRangeとは?基本構文から内部の仕組みまで徹底解説

Range(レンジ)とは何か?

RubyにおけるRange(範囲オブジェクト)は、開始値と終了値を持ち、その2つの値にまたがる連続的なシーケンスを簡単に作成できるオブジェクトです。

例えば、1から20までの連続した整数や、"a"から"z"までのアルファベットなどを表現できます。

この記事で解説する主なトピック:

  • RubyでのRangeの作り方
  • stepメソッドとinclude?メソッドの使い方
  • Rangeの内部的な仕組み

それでは早速見ていきましょう!

Rangeの基本構文をおさらい

まずは、Rubyの基本的なRangeの書き方を確認します。

(1..20)

Rangeを定義するだけであれば、括弧は必須ではありません。ただし、Rangeオブジェクト自身に対してメソッドを呼び出したい場合は括弧が必要です。括弧がないと、Rangeそのものではなく、範囲内の2番目の要素に対してメソッドが呼ばれてしまいます。

また、RangeクラスはEnumerableモジュールをインクルードしているため、配列に変換しなくても強力な繰り返し処理用メソッドをそのまま利用できます。

stepメソッドでn個ずつ要素を取り出す

Rangeクラスには、stepメソッドのような便利なメソッドも用意されています。

使用例:

(10..20).step(2).to_a
# => [10, 12, 14, 16, 18, 20]

stepメソッドを使うと、引数として渡した数値nずつ増加させながら範囲を反復処理できます。

include?とcover?の違い

step以外にも、Rangeの代表的なメソッドとしてcover?include?があります。

この2つは同じ動作をすると思いがちですが、それは間違いです。実際には挙動が明確に異なります。

include?メソッドは、名前の通り値が範囲内に含まれているかどうかをチェックします。これは、RangeArrayに展開して、その中に要素があるか確認するのと同等の動作です。

一方、cover?は範囲の先頭と終端の値に対してのみチェックを行う(begin <= obj <= end)ため、予想外の結果になることがあります。

使用例:

('a'..'z').include? "cc" # => false
('a'..'z').cover? "cc"   # => true

このcover?の例は、次の比較と同じ意味になります。

"a" <= "cc" && "cc" <= "z"

これがtrueを返す理由は、文字列が1文字ずつ比較されるためです。「a」は「c」より前にあるので、最初の文字以降は無視されます。

Rangeは内部でどのように実装されているのか

Rangeは数値や文字に限られません。比較演算子である<=>と、次の値を返すsuccメソッドを実装していれば、どんなオブジェクトでも使えます。

例えば、日付オブジェクトで範囲を作ると次のようになります。

require 'date'

t1 = Date.today
t2 = t1 + 30

next_30_days = t1..t2

# 使用例:今後30日以内の金曜日の「日」を取り出す
next_30_days.select(&:friday?).map(&:day)

では、Rangeはどのような仕組みで動いているのでしょうか?以下は、シンプルなRangeの挙動を再実装したサンプルコードです。

def range(a, b)
  # 最初の要素が2番目より大きい場合、
  # 連続的な範囲ではないので空を返す
  return [] if a > b

  out = []

  # 最初の要素が2番目の要素と一致するまで繰り返す
  while a != b
    out << a
    a = a.next
  end

  # 最後の要素を追加(両端を含む範囲)
  # 結果は暗黙の戻り値として返される
  out << a
end

p range 1, 10
p range 'a', 'z'

この実装の考え方は、最初のオブジェクトに対してnextメソッドを呼び続け、2番目のオブジェクトと等しくなるまで進めるというものです。最終的には必ず一致するという前提に立っています。

独自クラスでRangeを扱えるようにする

多くの場合、Rangeは数値や文字の範囲で使われますが、自分で定義したクラスでも範囲操作を扱えるようにしておくと、表現の幅が大きく広がります。

使用例:

class LetterMultiplier
  attr_reader :count

  include Comparable

  def initialize(letter, count)
    @letter = letter
    @count  = count
  end

  def succ
    self.class.new(@letter, @count + 1)
  end

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

a = LetterMultiplier.new('w', 2)
b = LetterMultiplier.new('w', 8)

# 範囲内の全要素を配列として出力
p Array(a..b)

ここでのポイントは、<=>succの2つのメソッドを正しく実装することです。

さらに、include?メソッドを使いたい場合は、Comparableモジュールをインクルードする必要があります。このモジュールにより、<=>の結果をもとに、==<>などの比較メソッドが自動的に提供されます。

まとめ

この記事では、RubyのRangeがどのように動作するのかを詳しく解説しました。この知識を活かせば、範囲オブジェクトをより深く理解できるだけでなく、範囲操作に対応した独自のオブジェクトを実装することも可能になります。

  1. RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法

    Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。 そもそもStructとは何か? StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。 具体例を挙げてみましょう。 2つの座標(xとy)を持つPointというデータを表したい場合、この

  2. Spotifyの共同プレイリストとは?作り方と友達への共有方法を徹底解説

    Spotifyでは、自分の音楽センスを世界に発信できる「公開プレイリスト」機能を利用できます。魅力的なプレイリストを作れば、多くの人にあなたの音楽コレクションを聴いてもらえるチャンスです。ただし、Spotifyにはプロが作成したプレイリストも多数存在するため、差別化が重要です。それでも、いくつかの簡単なステップで誰でもプレイリストを共有できます。さらに、Spotifyにはもう一つユニークな機能があります。それが「共同プレイリスト(Collaborative Playlist)」です。この機能を使えば、単にプレイリストを公開するだけでなく、友人たちにも曲を追加してもらい、さまざまな音楽のテイスト