【Ruby】Rangeオブジェクトの便利なメソッドを使いこなそう

  • 2021年7月21日
  • 2021年10月16日
  • Ruby

Rubyでは、Rangeオブジェクトと呼ばれる特殊なオブジェクトを取扱うことができます。

Rangeオブジェクトは、値の判定処理繰り返し処理を実装するうえで便利なオブジェクトです。

今回は、Rangeオブジェクトを取り扱うために覚えておきたい、Rangeクラスの取り扱い方についてサンプルプログラムを適宜提示しながら解説します。加えて、Rangeオブジェクトの繰り返し処理・判定処理への活用法についても解説しております。ぜひ参考にしてください。

なお、Rubyの魅力や特徴については以下の記事で詳しく解説していますので、こちらもぜひご覧くださいね。

おすすめ記事

みなさんこんにちは! 今回の記事は、 悩みを抱えた人 ・Rubyってどんなプログラミング言語なの?・Rubyを勉強すると将来役に立つかな?・Rubyのおすすめの学習方法が知りたい! というお悩みを解決する記事にな[…]

Rubyとは?その特徴や将来性、おすすめの学習方法まで網羅的にご紹介します

レバテックフリーランス … 業界最大級の案件数業界トップクラスの高単価報酬を誇る最大手のサービスです。実績豊富なコーディネーターが丁寧な対応をしてくれるため、案件の無理な提案はありません。フリーランスで生きていくためにはまず登録しておきましょう。
MidWorks … フリーランス賠償責任保障生命保険の折半など、フリーランスでありながらも正社員並みの保障を受けられるのが特徴です。また、経験豊富なキャリアコンサルタントによる手厚いサポートも受けられるため、安定したフリーランス生活を送りたい方には特におすすめのサービスです。
ポテパンフリーランス … IT業界・技術に詳しいコンサルタントが担当してくれるため、こちらの要望をきちんと理解した上で案件を紹介してくれます。また、案件情報のみならず、フリーランスのイロハについても教えてくれるため、フリーランスとして初めて活動される方には特におすすめのサービスです。

レバテックキャリア … ITエンジニアが利用したい転職エージェントNo.1にも選ばれており、年収600万円以上のハイクラス求人を5,000件以上も保有しています。エンジニアが転職を考えた時にまず初めに登録しておくべきサービスです。
Tech Stars Agent … Tech Stars Agentでは、担当エージェントが全員エンジニア出身のため、スキルやキャリアを見据えたきめ細かな転職支援が受けられます。運営元の株式会社Branding Engineerは、独立支援サービス「MidWorks」も展開しているため、独立を視野に入れたサポートも受けられます
転職ドラフト … 年収UP率93.8%/平均年収UP額126万円と圧倒的な年収UP率を誇るイベント型のエンジニア向け 転職サービスです。毎月1回開催され、厳選された優良IT/Web系企業約150社からダイレクトスカウトを受け取ることができます。年収アップを目指す方は登録必須です。


Rangeオブジェクトの作成方法

Rangeオブジェクトを使いこなせるようになるためには、最初にどのようにRangeオブジェクトを生成するのかを理解することが先決です。Rangeオブジェクトを作成する方法は、以下の2種類が存在します。

  • Range.newメソッドを呼び出す
  • 演算子「..」「…」を利用する

Range.newメソッドを呼び出す

1つ目の方法は、Rangeクラスに用意されている、newメソッドを呼び出すことです。早速ですが、以下のサンプルコードをご覧ください。

# Range.newメソッドで引数の値を範囲とするオブジェクトを生成
sample_range_1 = Range.new(3, 8)

# 3以上8以下のRangeオブジェクトが生成される
p sample_range_1 # 3..8

# 第三引数にtrueを設定すると、
# 終端を除くRangeオブジェクトが生成される
sample_range_2 = Range.new(3, 8, true)

# 3以上8「未満」のRangeオブジェクトが生成される
p sample_range_2 # 3...8

Range.newメソッドは、引数を3つ持つメソッドで、Rangeオブジェクトを返します。各引数の意味は、以下の表の通りです。

引数意味
第一引数Rangeオブジェクトの最初の値
第二引数Rangeオブジェクトの最後の値
第三引数true: 最後の値を範囲に含まない
省略:最後の値を範囲に含む

2行目におけるRange.newメソッド呼び出しでは、第一引数は「3」、第二引数は「8」を指定し第三引数は省略されているため、「3から8」を範囲とするRangeオブジェクトが作成されることが分かります。

一方、9行目におけるRange.newメソッドの呼び出しでは、第三引数にtrueを設定しています。この場合、作成されるRangeオブジェクトの範囲に、最後の値は含まれません。したがって、こちらは「3から7」を範囲とするRangeオブジェクトとなります。

ここで、putsメソッドによってRangeオブジェクトを表示した結果を確認してみましょう。コンソールには、それぞれ「3..8」「3…8」と表示されます。特に後者については、内部的に範囲が3から7であるにも関わらず、画面上では範囲外の値である8が表示されています。

勘違いしがちなポイントなので、ドットの個数を確認して範囲外であるかを確認するように心がけましょう。

演算子「..」「…」を利用する

2つ目は、演算子「..」「…」を利用することです。サンプルコードは以下の通りです。

# 「..」演算子(ドット2つ)でRangeオブジェクト作成
sample_range_3 = 3..8

# 3以上8以下を範囲とする
p sample_range_3 # 3..8

# 「...」演算子(ドット3つ)によるRangeオブジェクト作成
sample_range_4 = 3...8

# 3以上8未満を範囲とする
p sample_range_4 # 3...8

ドット2つの「..」演算子を使って、範囲の開始値と終了値を記述することで、Range.newメソッド呼び出し時と同様のRangeオブジェクトを作成できます。

ドット3つの「…」演算子を使う場合は、Range.newメソッドの第三引数にtrueを設定した場合と同様で、終了値を範囲に入れないRangeオブジェクトが返されることが分かります。

ご覧の通り、Range.newメソッドよりも少ない記述量で簡単にRangeオブジェクトを作成できます。ソースコードのサイズを気にする場面では、「..」「…」演算子を用いるのが最適です。ただし、演算子のドットの個数を間違えないように注意する必要があります。

Rangeオブジェクトで取り扱える値

Rangeオブジェクトは、整数以外の値を取り扱うことができます。以下のサンプルコードは、アルファベットやかな文字を値としたRangeオブジェクトの作成例です。

# アルファベットを範囲としたRangeオブジェクト
char_range = 'a' .. 'z'
p char_range # "a".."z"

char_range = 'A' .. 'Z'
p char_range # "A".."Z"

# かなを範囲としたRangeオブジェクト
char_range = 'あ' .. 'ん'
p char_range # "あ".."ん"

char_range = 'ア' .. 'ン'
p char_range # "ア".."ン"

整数以外の値をRangeオブジェクトの範囲とする場合、2行目・5行目のような1バイト文字は、ASCIIコードの順序に従って作成されます。一方、9行目・12行目のようなマルチバイト文字は、UTF-8の順序に従って作成されます。

範囲を設定しないRangeオブジェクト

Range.newメソッドの引数や演算子にnilを設定することで、範囲が未設定のRangeオブジェクトを作成できます。サンプルコードは以下の通りです。

# 第一引数がnil
# →開始値が設定されていないRangeオブジェクトを作成
not_set_range = Range.new(nil, 100)

p not_set_range # ..100

# 第二引数がnil
# →終了値が設定されていないRangeオブジェクトを作成
not_set_range = Range.new(20, nil)

p not_set_range # 20..

# 第一引数・第二引数ともにnil
# →全範囲を示すRangeオブジェクトを作成
not_set_range = Range.new(nil, nil)

p not_set_range # nil..nil

# 演算子を利用する場合も同様
not_set_range_1 = nil..234
not_set_range_2 = 234..nil

p not_set_range_1 # ..234
p not_set_range_2 # 234..

# 演算子を利用する場合、nilを省略可能
not_set_range_1 = ..234
not_set_range_2 = 234..

p not_set_range_1 # ..234
p not_set_range_2 # 234..

# ただし全範囲のオブジェクトはnilを省略できないことに注意
not_set_range = nil..nil
# not_set_range = ... このような記述はエラーとなる

p not_set_range # nil..nil

引数をnilに設定したり、演算子の片方をnilに設定、または省略した場合、putsメソッドでRangeオブジェクトをコンソール出力したときに「..234」や「234..」のように表示されます。

一方、開始値・終了値双方をnilとした場合は、全範囲であることを示すRangeオブジェクトが作成され「nil..nil」のように出力されます。

注意点として、全範囲を示すRangeオブジェクトを「..」「…」演算子で作成する場合、nilは省略できません(35行目)。もし、nilを省略するとシンタックスエラーとなりプログラムの実行が中断されるので、注意してください。

Rangeオブジェクトによる値の判定処理

Rangeオブジェクトを利用すれば、評価対象の値がRangeオブジェクトで定義した範囲内に存在するか否かを判定する処理を実装できます。ここでは、Rangeオブジェクトによる値の判定処理で利用するメソッドを、2つ紹介します。

include?メソッド

1つ目に紹介するメソッドは、include?メソッドです。

include?メソッドは、Rangeオブジェクトをレシーバとし、判定対象となるオブジェクト(変数)を引数に持ちます。判定対象のオブジェクトがRangeオブジェクトの範囲内であればtrue、そうでなければfalseを返します。

以下のサンプルコードで、具体的に動作確認してみましょう。

number_range_1 = 200..400
number_range_2 = 200...400
source_number = 400

# 終了値を含むRangeオブジェクトで判定した場合
if number_range_1.include?(source_number)
  p source_number.to_s + "は範囲に含まれます。" # この行が実行される
else
  p source_number.to_s + "は範囲に含まれません。"    
end

# 終了値を含まないRangeオブジェクトで判定した場合
if number_range_2.include?(source_number)
  p source_number.to_s + "は範囲に含まれます。"
else
  p source_number.to_s + "は範囲に含まれません。"  # この行が実行される
end

サンプルコードでは「200以上400以下」「200以上400未満」のRangeオブジェクトを各々利用して、400が格納された変数が範囲に含まれているかを判定する処理を、include?メソッドで実装しています。

終了値を含むRangeオブジェクトの場合は、変数の中身が範囲内に含まれているため、include?メソッドはtrueを返します(6行目)。一方、終了値を含まないRangeオブジェクトであれば、変数の中身は範囲外であるため戻り値はfalseです。

文字列のRangeオブジェクトで値判定処理を実装することもできます。ただし、include?メソッドによる判定処理では、「===」演算子を使った値の比較によって行われているため、連続的な値の判定処理を実装することができません。その場合は、次節で紹介するcover?メソッドを利用しましょう。

cover?メソッド

2つ目に紹介するメソッドは、cover?メソッドです。include?メソッドと同じく、Rangeオブジェクトをレシーバに、1つの引数を持ちます。判定結果もinclude?メソッドと同様です。

include?メソッドと異なる点として、cover?メソッドによる判定処理では、「<=>」演算子による値の比較を内部で実行しています。そのため、include?メソッドでは実装できないような値判定処理をcover?メソッドでは実装できます。以下のサンプルコードをご覧ください。

char_range = 'a'..'g'
source_word = 'dog'

p "include?メソッド:" + char_range.include?(source_word).to_s # "include?メソッド:false"
p "cover?メソッド:" + char_range.cover?(source_word).to_s     # "cover?メソッド:true"

単語「dog」が「aからg」の範囲内に含まれているかを判定する処理を、include?メソッドとcover?メソッドの両方で実装しています。

「aからg」のため、dogは範囲内であると言えますが、include?メソッドでは「===」演算子による値比較を行っており、頭文字が同じであっても、dとdogは同一ではないためfalseが返されます。一方、cover?メソッドで判定した場合、戻り値はtrueです。

また、cover?メソッドでは引数にRangeオブジェクトを指定することができます。その際は、範囲の左端と右端それぞれがレシーバのRangeオブジェクトの範囲を超えているか否かを判定するように動作します。

両端ともに範囲内、左端・右端いずれか一方が範囲外、両端ともに範囲外の4パターンで、判定結果がどのように変化するのか、以下のサンプルコードで確認してください。

normal_temp = 5..35

source_temp_1 = 10..20 # 両端ともに範囲内
source_temp_2 = 0..20  # 左端のみ範囲外
source_temp_3 = 30..40 # 右端のみ範囲外
source_temp_4 = 0..40  # 両端ともに範囲外

p "10~20:" + normal_temp.cover?(source_temp_1).to_s # "10~20:true"
p " 0~20:" + normal_temp.cover?(source_temp_2).to_s # " 0~20:false"
p "30~40:" + normal_temp.cover?(source_temp_3).to_s # "30~40:false"
p " 0~40:" + normal_temp.cover?(source_temp_4).to_s # " 0~40:false"

Rangeオブジェクトによる繰り返し処理

Rangeクラスは、配列やハッシュのような繰り返し処理が可能なクラスとして分類されています。したがって、eachメソッドやfilterメソッドなど、配列やハッシュを取り扱う際に利用できるメソッドをRangeオブジェクトに対して利用可能です。

ここでは、eachメソッドとfilterメソッドを例に、Rangeオブジェクトで繰り返し処理を実装する方法について、簡単に解説します。

eachメソッド

eachメソッドを使う場合は、配列やハッシュの場合と同じようにRangeオブジェクトをレシーバとして呼び出し、引数に要素に対して適用する処理を記述します。サンプルコードは以下の通りです。

ary = []

# 整数10から16を要素とする配列を作成
res = (10..16).each { |n| ary.push(n) }

p ary # [10, 11, 12, 13, 14, 15, 16]

filterメソッド

filterメソッドの使い方もeachメソッドと同様です。filterメソッドの場合は、判定結果にマッチした要素のみで構成される配列が戻り値として返されます。サンプルコードは以下の通りです。

mul3_con3_ary = []

# 3の倍数と3の突く数字のみを抽出
mul3_con3_ary = (1..40).filter { |n|
     (n % 3 == 0) or n.to_s.include?("3")
}

p mul3_con3_ary # [3, 6, 9, 12, 13, 15, 18, 21, 23, 24, 27, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]

お仕事の途中ですが、少し一休みして、転職独立について考えてみませんか🙌?

現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】

レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】

MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】

日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲

最後に

さて、ここまでRubyのRangeクラスの取り扱い方について解説してきましたがいかがでしたか?

今回ご紹介した通り、Rangeクラスには便利なメソッドが多数準備されており、これらを使いこなすことで、コードや処理の簡素化/効率化が図れます。

実際の開発でも頻繁に登場するものばかりですので、ぜひこの機会にマスターしておいてくださいね。

このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。

最後まで読んで頂きありがとうございました。