Skip to content

Commit 19ed28e

Browse files
transclaude
andcommitted
Add Range.intersection and Range#intersection
Find the shared region of multiple ranges. Returns nil if ranges do not overlap. Works with any comparable type (integers, floats, dates, strings, etc.). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1bd8041 commit 19ed28e

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

HISTORY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Changes:
1515
* Add `Array#indexes` / `Array#index_all` to find all matching indexes. (PR#294)
1616
* Add `String#dashcase` for kebab-case conversion. (PR#297)
1717
* Add `Binding#caller_locations`.
18+
* Add `Range.intersection` and `Range#intersection` for finding the shared
19+
region of multiple ranges. Works with any comparable type.
1820
* Add `Kernel#tee` — block-less method chaining via Tee/Functor, replaces `tap` override.
1921
* Add `Tee` as alias for `Functor` (gradual rename).
2022
* Rename `Hash#to_proc` to `Hash#setter` (avoids clash with Ruby 2.3's `Hash#to_proc`
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class Range
2+
3+
# Returns the intersection of two or more ranges — the region they
4+
# all share. Returns nil if the ranges do not overlap.
5+
#
6+
# Works with any comparable type: integers, floats, dates, strings, etc.
7+
#
8+
# Examples
9+
#
10+
# Range.intersection(1..10, 5..15) #=> 5..10
11+
# Range.intersection(1..5, 3..7, 4..9) #=> 4..5
12+
# Range.intersection(1..3, 5..7) #=> nil
13+
# Range.intersection(1.0..5.0, 2.5..4.5) #=> 2.5..4.5
14+
#
15+
# Also available as an instance method:
16+
#
17+
# (1..10).intersection(5..15) #=> 5..10
18+
#
19+
# CREDIT: Trans
20+
21+
def self.intersection(*ranges)
22+
return nil if ranges.empty?
23+
ranges.reduce do |result, r|
24+
return nil unless result.overlap?(r)
25+
new_first = result.first > r.first ? result.first : r.first
26+
new_last = result.last < r.last ? result.last : r.last
27+
new_first..new_last
28+
end
29+
end
30+
31+
# Returns the intersection of this range with one or more other ranges.
32+
# Returns nil if the ranges do not overlap.
33+
#
34+
# (1..10).intersection(5..15) #=> 5..10
35+
# (1..10).intersection(5..15, 8..20) #=> 8..10
36+
# (1..3).intersection(5..7) #=> nil
37+
#
38+
def intersection(*others)
39+
Range.intersection(self, *others)
40+
end
41+
42+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
covers 'facets/range/intersection'
2+
3+
test_case Range do
4+
5+
class_method :intersection do
6+
7+
test "two overlapping ranges" do
8+
Range.intersection(1..10, 5..15).assert == 5..10
9+
end
10+
11+
test "three overlapping ranges" do
12+
Range.intersection(1..5, 3..7, 4..9).assert == 4..5
13+
end
14+
15+
test "non-overlapping ranges" do
16+
Range.intersection(1..3, 5..7).assert == nil
17+
end
18+
19+
test "floats" do
20+
Range.intersection(1.0..5.0, 2.5..4.5).assert == 2.5..4.5
21+
end
22+
23+
test "identical ranges" do
24+
Range.intersection(1..5, 1..5).assert == 1..5
25+
end
26+
27+
test "one range contains the other" do
28+
Range.intersection(1..10, 3..5).assert == 3..5
29+
end
30+
31+
end
32+
33+
method :intersection do
34+
35+
test "instance method" do
36+
(1..10).intersection(5..15).assert == 5..10
37+
end
38+
39+
test "multiple arguments" do
40+
(1..10).intersection(5..15, 8..20).assert == 8..10
41+
end
42+
43+
end
44+
45+
end

0 commit comments

Comments
 (0)