3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rangeの共通部分(交差)を求めるメソッドが無かったので作った

Last updated at Posted at 2025-12-21

背景

必要に迫られて、2つのRangeオブジェクト間の共通部分(交差)を求めるメソッドを作成しました。
なお、現状の Ruby 3.4 には、この交差を求めるメソッドはありません。てっきり、既にあると思っていたのですが(後述
因みに、困ったときの ActiveSupport にも Range 拡張に交差を求めるメソッドは、ありませんでした。

要求仕様

  • 動作環境は Ruby 3.4
    • Range#overlap? が使える前提です
  • 無限範囲に対応必要
    • 始端や終端に nil を指定した、片側が無限の範囲です
  • 終端を含む/含まない、に対応必要
    • exclude_end 指定があります
  • Rangeの始端や終端は、比較可能
    • 今回、具体的には Date が入ります
  • 空のRangeは、与えられない
    • 今回の具体的な用途による限定です

実装

range_extensions.rb
module RangeExtensions
  class Intersection
    def initialize(range1, range2)
      raise ArgumentError, 'values must be a Ranges' unless range1.is_a?(Range) && range2.is_a?(Range)
      
      @range1 = range1
      @range2 = range2
    end
    
    def call = overlap? ? intersection : nil
    
    private
    attr_reader :range1, :range2
    
    def intersection = Range.new(new_begin, new_end, new_exclude)
    
    def overlap? = range1.overlap?(range2)
    
    def new_begin = [range1.begin, range2.begin].compact.max
    
    def new_end = [range1.end, range2.end].compact.min
    
    def new_exclude = same_end? ? same_end_exclude : different_end_exclude
    
    def same_end? = end_from_range1? && end_from_range2?

    # 両者の終端が同じ場合、どちらかが exclude ならば、交差も exclude になる
    def same_end_exclude = range1.exclude_end? || range2.exclude_end?

    # 両者の終端が異なる場合、採用した終端の exclude が、交差の exclude になる
    def different_end_exclude = end_from_range1? ? range1.exclude_end? : range2.exclude_end?
    
    def end_from_range1?  = (new_end == range1.end)
    
    def end_from_range2? = (new_end == range2.end)
  end
  
  refine Range do
    def intersection(other) = Intersection.new(self, other).call
  end
end

生成AIの利用

  1. まず自力でコード実装します1
  2. メジャーな複数の生成AI2に対して「このコードの解説と評価をしなさい」とだけ指示して、結果を出力
  3. 得られた結果を見比べて、意図の誤解や誤読がなされていないことを確認しておき
  4. 提示された改良点を理解し、妥当かどうか判断した上で
  5. 参考になる部分をコードに反映し、数回の修正を実施しました

例えば以下の実装は、生成AIの提案を参考にして実装しました。
片側無限の場合、端点が nil になる可能性を踏まえて、最大/最小を求めています。

    def new_begin = [range1.begin, range2.begin].compact.max
    def new_end = [range1.end, range2.end].compact.min

使用方法

  • クラス内で using RangeExtensions すると、Range#intersection が利用できます
  • 共通部分(交差)が無い場合、nil を返します
  • 共通部分(交差)が一点のみの場合、その一点を含むRange (n..n) を返します
  • 終端を含まない場合(exclude)、終端は「範囲が重なっていない」と見做します

実行例

using RangeExtensions

# 基本的な交差
(1..8).intersection(3..10)  #=> 3..8

# 終端を含まない場合
(1...8).intersection(3..10) #=> 3...8
(1...8).intersection(3..8)  #=> 3...8

# 交差がない場合
(1..8).intersection(10..18) #=> nil

# 終端を含むか含まないかの違い
(1..3).intersection(3..5)   #→ 3..3
(1...3).intersection(3..5)  #→ nil

# 片側無限の範囲
(1..).intersection(..10)    #=> 1..10
(..5).intersection(..8)     #=> ..5
(1..).intersection(1...5)   #=> 1...5

# Date での実用例
require 'date'

period1 = Date.new(2025, 12, 25)..Date.new(2026, 1, 15)
period2 = Date.new(2025, 12, 31)..Date.new(2026, 4, 1)
period1.intersection(period2)
=> #<Date: 2025-12-31>..#<Date: 2026-01-15>
# Date内部表現は省略しています

なぜ最初から入っていないのか?

Rangeの共通部分(交差)を求める、という用途は一般的なものだと思います。また、前記のコードを実装してみて、それほど複雑ではない、という感触を持ちました。

Ruby の場合、こうしたケースだと「きっと既にあるに違いない」という予測が当たっていることが多かった3ため、Ruby 標準ライブラリ(Core)にも ActiveSupport 拡張にも、どちらにも無かった点が気になりました。
特に、Range#overlap? が以前から実装済みだった4ことから、きっと Range#intersection もあるに違いないと思っていたので意外でした。

この点を少し調べたところ、2020年ころに既に Issue #16757 "Add intersection to Range" として提案が出ていたものの、現時点ではOpenなままでした。

これはどうやら、汎用の仕様を決めることが難しかったらしく、

  • 空集合を表現する標準的な Range オブジェクトが存在しない
    • nil だとメソッドチェーンがやりにくくなる
  • 連続値(区間)と離散値(列)の違い
  • Range#step の扱い
  • Array#intersection との一貫性

などなど、ちょっと合意が簡単には得られない課題があるようです。
確かに、今回の実装に際して生成AIのコメントでも、仕様面や境界条件の扱いに関する指摘が多かったです。

まとめ

今回の様に、特定の用途に絞るなら、仕様の割り切りが出来るので、比較的簡単な実装が可能です。
一方で、標準に含めるためには、仕様面での課題から、難しい点が多いと思われます。

  1. 私の場合、いきなり生成AIに指示してコードを実装させるより、結果的に速く仕上がることが多いためです。

  2. ChatGPT、Gemini、Microsoft Copilot、Claude、を無料枠内で利用しました。

  3. 経験則として「これは無いと不便だよね~みんな使うだろうし」と思う機能は、たいてい既に在りました。

  4. Ruby 3.3 で Range#overlap? が導入されました。なお、ActiveSupport 拡張には、それ以前からありました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?