From c704bc222aeee82f23510e44d1700ca0cd150e1f Mon Sep 17 00:00:00 2001 From: CTC97 Date: Sun, 20 Oct 2024 21:46:38 -0400 Subject: [PATCH 01/15] ranges - union; intersection; overlaps? --- src/range.cr | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/range.cr b/src/range.cr index e8ee24b190cb..b282324b6a8a 100644 --- a/src/range.cr +++ b/src/range.cr @@ -252,6 +252,58 @@ struct Range(B, E) {% end %} end + # Returns the union of this range, and another. + # Returns 0..0 if there is no overlap. + # + # ``` + # (1..5).union(5..10) # => 1..10 + # (1...10).union(1..10) # => 1..10 + # ``` + def union(other : Range) + if self.end < other.begin || other.end < self.begin + return 0..0 + end + + larger = (self.end > other.end) + + if (larger && self.excludes_end?) || (!larger && other.excludes_end?) + return (self.begin < other.begin ? self.begin : other.begin)...(self.end > other.end ? self.end : other.end) + end + + (self.begin < other.begin ? self.begin : other.begin)..(self.end > other.end ? self.end : other.end) + end + + # Returns the intersection of this range, and another. + # Returns 0..0 if there is no overlap. + # + # ``` + # (2..10).intersection(0..8) # => 2..8 + # (1...10).intersection(7..12) # => 7...10 + # ``` + def intersection(other : Range) + if self.end < other.begin || other.end < self.begin + return 0..0 + end + + larger = (self.end > other.end) + + if (!larger && self.excludes_end?) || (larger && other.excludes_end?) + return (self.begin > other.begin ? self.begin : other.begin)...(self.end < other.end ? self.end : other.end) + end + + (self.begin > other.begin ? self.begin : other.begin)..(self.end < other.end ? self.end : other.end) + end + + # Returns `true` if this range overlaps with another range. + # + # ``` + # (1..10).overlaps?(5..9) # => true + # (1...10).overlaps?(10..11) # => false + # ``` + def overlaps?(other : Range) : Bool + !(self.end <= other.begin || other.end <= self.begin) + end + # Returns `true` if this range excludes the *end* element. # # ``` From 22688e92b35fb023573a9472e917e39cc3b84b3d Mon Sep 17 00:00:00 2001 From: CTC97 Date: Sun, 20 Oct 2024 21:49:43 -0400 Subject: [PATCH 02/15] crystal tool format --- src/range.cr | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/range.cr b/src/range.cr index b282324b6a8a..bc1614abe759 100644 --- a/src/range.cr +++ b/src/range.cr @@ -256,7 +256,7 @@ struct Range(B, E) # Returns 0..0 if there is no overlap. # # ``` - # (1..5).union(5..10) # => 1..10 + # (1..5).union(5..10) # => 1..10 # (1...10).union(1..10) # => 1..10 # ``` def union(other : Range) @@ -277,7 +277,7 @@ struct Range(B, E) # Returns 0..0 if there is no overlap. # # ``` - # (2..10).intersection(0..8) # => 2..8 + # (2..10).intersection(0..8) # => 2..8 # (1...10).intersection(7..12) # => 7...10 # ``` def intersection(other : Range) @@ -286,7 +286,6 @@ struct Range(B, E) end larger = (self.end > other.end) - if (!larger && self.excludes_end?) || (larger && other.excludes_end?) return (self.begin > other.begin ? self.begin : other.begin)...(self.end < other.end ? self.end : other.end) end @@ -295,9 +294,9 @@ struct Range(B, E) end # Returns `true` if this range overlaps with another range. - # + # # ``` - # (1..10).overlaps?(5..9) # => true + # (1..10).overlaps?(5..9) # => true # (1...10).overlaps?(10..11) # => false # ``` def overlaps?(other : Range) : Bool From 1775ddb00f0b949424e6fdce21b65646208d41b4 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Sun, 20 Oct 2024 21:52:25 -0400 Subject: [PATCH 03/15] nit :broom: --- src/range.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/range.cr b/src/range.cr index bc1614abe759..d5f2fa1852b0 100644 --- a/src/range.cr +++ b/src/range.cr @@ -265,7 +265,6 @@ struct Range(B, E) end larger = (self.end > other.end) - if (larger && self.excludes_end?) || (!larger && other.excludes_end?) return (self.begin < other.begin ? self.begin : other.begin)...(self.end > other.end ? self.end : other.end) end From 04932744894e907e31b98fa4abb3ca622648c000 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Mon, 21 Oct 2024 22:04:42 -0400 Subject: [PATCH 04/15] nil returns, specs --- spec/std/range_spec.cr | 19 +++++++++++++++++++ src/range.cr | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index 9b89f2cd4765..f25dd5e925e3 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -79,6 +79,25 @@ describe "Range" do (1...5).includes?(5).should be_false end + it "does union" do + (1..5).union(3..7).should eq(1..7) + (1..10).union(1...15).should eq(1...15) + (1...10).union(1..15).should eq(1..15) + (1..5).union(10..20).should eq(nil) + end + + it "does intersection" do + (1..5).intersection(3..7).should eq(3..5) + (1..10).intersection(1...15).should eq(1..10) + (1...10).intersection(1..15).should eq(1...10) + (1..5).intersection(10..20).should eq(nil) + end + + it "overlaps?" do + (1...10).overlaps?(10..11).should eq(false) + (1..10).overlaps?(5..9).should eq(true) + end + it "does to_s" do (1...5).to_s.should eq("1...5") (1..5).to_s.should eq("1..5") diff --git a/src/range.cr b/src/range.cr index d5f2fa1852b0..f78661a6ae07 100644 --- a/src/range.cr +++ b/src/range.cr @@ -261,7 +261,7 @@ struct Range(B, E) # ``` def union(other : Range) if self.end < other.begin || other.end < self.begin - return 0..0 + return nil end larger = (self.end > other.end) @@ -281,7 +281,7 @@ struct Range(B, E) # ``` def intersection(other : Range) if self.end < other.begin || other.end < self.begin - return 0..0 + return nil end larger = (self.end > other.end) From 8bda941f47e8dfbb2cbbc56e473adf90c24432dc Mon Sep 17 00:00:00 2001 From: CTC97 Date: Tue, 22 Oct 2024 21:33:38 -0400 Subject: [PATCH 05/15] additional tests + adjacency ambiguity --- spec/std/range_spec.cr | 91 +++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index f25dd5e925e3..1aa55b925d49 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -80,23 +80,84 @@ describe "Range" do end it "does union" do - (1..5).union(3..7).should eq(1..7) - (1..10).union(1...15).should eq(1...15) - (1...10).union(1..15).should eq(1..15) - (1..5).union(10..20).should eq(nil) - end - + # Int ranges + (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges + + # TODO: This test will fail - + # In an integer setting this case is doable. We can simply check adjaceny with +-1. + # However, how do we define an adjacent range for other data types? + # Examples of adjacency ambiguity: + # - Floats: Is 4.1's upper adjacent value 4.2 or 4.11, etc.? + # - Times: Is a time's adjacent value +1 second or +1 hour, etc.? + # + # How do we define adjacency? + # + # (1..5).union(6..10).should eq(1..10) # Adjacent ranges + + (1..5).union(10..15).should eq(nil) # Disjoint integer ranges + + # Float ranges + (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges + (1.0..2.5).union(3.0..4.0).should eq(nil) # Non-overlapping float ranges + + # String ranges + ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges + ('a'..'c').union('d'..'f').should eq(nil) # Non-overlapping string ranges + + # Time ranges + t1 = Time.local(2024, 10, 1) + t2 = Time.local(2024, 10, 5) + t3 = Time.local(2024, 10, 10) + t4 = Time.local(2024, 10, 15) + (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges + (t1..t2).union(t3..t4).should eq(nil) # Disjoint time ranges + end + it "does intersection" do - (1..5).intersection(3..7).should eq(3..5) - (1..10).intersection(1...15).should eq(1..10) - (1...10).intersection(1..15).should eq(1...10) - (1..5).intersection(10..20).should eq(nil) - end - + # Int ranges + (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges + (1..5).intersection(6..10).should eq(nil) # Non-overlapping integer ranges + (1..5).intersection(5..10).should eq(5..5) + + # Float ranges + (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges + (1.0..2.5).intersection(3.0..4.0).should eq(nil) # Non-overlapping float ranges + + # String ranges + ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges + ('a'..'c').intersection('d'..'f').should eq(nil) # Non-overlapping string ranges + + # Time ranges + t1 = Time.local(2024, 10, 1) + t2 = Time.local(2024, 10, 5) + t3 = Time.local(2024, 10, 10) + t4 = Time.local(2024, 10, 15) + (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges + (t1..t2).intersection(t3..t4).should eq(nil) # Non-overlapping time ranges + end + it "overlaps?" do - (1...10).overlaps?(10..11).should eq(false) - (1..10).overlaps?(5..9).should eq(true) - end + # Int ranges + (1..5).overlaps?(3..7).should eq(true) # Overlapping integer ranges + (1..5).overlaps?(6..10).should eq(false) # Non-overlapping integer ranges + + # Float ranges + (1.0..5.5).overlaps?(3.2..7.8).should eq(true) # Overlapping float ranges + (1.0..2.5).overlaps?(3.0..4.0).should eq(false) # Non-overlapping float ranges + + # String ranges + ('a'..'e').overlaps?('c'..'g').should eq(true) # Overlapping string ranges + ('a'..'c').overlaps?('d'..'f').should eq(false) # Non-overlapping string ranges + + # Time ranges + t1 = Time.local(2024, 10, 1) + t2 = Time.local(2024, 10, 5) + t3 = Time.local(2024, 10, 10) + t4 = Time.local(2024, 10, 15) + (t1..t3).overlaps?(t2..t4).should eq(true) # Overlapping time ranges + (t1..t2).overlaps?(t3..t4).should eq(false) # Non-overlapping time ranges + end + it "does to_s" do (1...5).to_s.should eq("1...5") From 47b0627795fa310803a24cdab93e843d9a834eb6 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Thu, 24 Oct 2024 22:30:56 -0400 Subject: [PATCH 06/15] code cleanup --- spec/std/range_spec.cr | 6 +++++- src/range.cr | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index 1aa55b925d49..a75773bec745 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -95,11 +95,14 @@ describe "Range" do # (1..5).union(6..10).should eq(1..10) # Adjacent ranges (1..5).union(10..15).should eq(nil) # Disjoint integer ranges - + + # Float ranges (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges (1.0..2.5).union(3.0..4.0).should eq(nil) # Non-overlapping float ranges + + # String ranges ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges ('a'..'c').union('d'..'f').should eq(nil) # Non-overlapping string ranges @@ -118,6 +121,7 @@ describe "Range" do (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges (1..5).intersection(6..10).should eq(nil) # Non-overlapping integer ranges (1..5).intersection(5..10).should eq(5..5) + #(1...1).intersection(0..10).should eq(nil) # Float ranges (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges diff --git a/src/range.cr b/src/range.cr index f78661a6ae07..ef91a03960e6 100644 --- a/src/range.cr +++ b/src/range.cr @@ -265,11 +265,11 @@ struct Range(B, E) end larger = (self.end > other.end) - if (larger && self.excludes_end?) || (!larger && other.excludes_end?) - return (self.begin < other.begin ? self.begin : other.begin)...(self.end > other.end ? self.end : other.end) - end - - (self.begin < other.begin ? self.begin : other.begin)..(self.end > other.end ? self.end : other.end) + Range.new( + self.begin < other.begin ? self.begin : other.begin, + self.end > other.end ? self.end : other.end, + exclusive: (larger && self.excludes_end?) || (!larger && other.excludes_end?), + ) end # Returns the intersection of this range, and another. @@ -285,11 +285,11 @@ struct Range(B, E) end larger = (self.end > other.end) - if (!larger && self.excludes_end?) || (larger && other.excludes_end?) - return (self.begin > other.begin ? self.begin : other.begin)...(self.end < other.end ? self.end : other.end) - end - - (self.begin > other.begin ? self.begin : other.begin)..(self.end < other.end ? self.end : other.end) + Range.new( + self.begin > other.begin ? self.begin : other.begin, + self.end < other.end ? self.end : other.end, + exclusive:(!larger && self.excludes_end?) || (larger && other.excludes_end?), + ) end # Returns `true` if this range overlaps with another range. From 9e236be8b428fe373f563e54d11c510ed36ae8a8 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Thu, 24 Oct 2024 22:40:39 -0400 Subject: [PATCH 07/15] nit --- src/range.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/range.cr b/src/range.cr index ef91a03960e6..5b8441d04e40 100644 --- a/src/range.cr +++ b/src/range.cr @@ -269,7 +269,7 @@ struct Range(B, E) self.begin < other.begin ? self.begin : other.begin, self.end > other.end ? self.end : other.end, exclusive: (larger && self.excludes_end?) || (!larger && other.excludes_end?), - ) + ) end # Returns the intersection of this range, and another. @@ -289,7 +289,7 @@ struct Range(B, E) self.begin > other.begin ? self.begin : other.begin, self.end < other.end ? self.end : other.end, exclusive:(!larger && self.excludes_end?) || (larger && other.excludes_end?), - ) + ) end # Returns `true` if this range overlaps with another range. From 75f9561033ee670b4080e82aa3bf8f062aa6b285 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Sat, 26 Oct 2024 15:59:17 -0400 Subject: [PATCH 08/15] cleanup :broom: --- src/range.cr | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/range.cr b/src/range.cr index 5b8441d04e40..cf66ca19eef6 100644 --- a/src/range.cr +++ b/src/range.cr @@ -260,9 +260,7 @@ struct Range(B, E) # (1...10).union(1..10) # => 1..10 # ``` def union(other : Range) - if self.end < other.begin || other.end < self.begin - return nil - end + return nil if self.end < other.begin || other.end < self.begin larger = (self.end > other.end) Range.new( @@ -280,9 +278,7 @@ struct Range(B, E) # (1...10).intersection(7..12) # => 7...10 # ``` def intersection(other : Range) - if self.end < other.begin || other.end < self.begin - return nil - end + return nil if self.end < other.begin || other.end < self.begin larger = (self.end > other.end) Range.new( From 5bb385755e27514504d5b17ffbc09a5fee32ad75 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Wed, 6 Nov 2024 23:10:25 -0500 Subject: [PATCH 09/15] adjacency check for unions --- spec/std/range_spec.cr | 5 +++-- src/range.cr | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index a75773bec745..cacb92d154ff 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -92,7 +92,7 @@ describe "Range" do # # How do we define adjacency? # - # (1..5).union(6..10).should eq(1..10) # Adjacent ranges + (1..5).union(6..10).should eq(1..10) # Adjacent ranges (1..5).union(10..15).should eq(nil) # Disjoint integer ranges @@ -105,7 +105,8 @@ describe "Range" do # String ranges ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges - ('a'..'c').union('d'..'f').should eq(nil) # Non-overlapping string ranges + ('a'..'c').union('e'..'f').should eq(nil) # Non-overlapping string ranges + ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges # Time ranges t1 = Time.local(2024, 10, 1) diff --git a/src/range.cr b/src/range.cr index cf66ca19eef6..397d8de06836 100644 --- a/src/range.cr +++ b/src/range.cr @@ -260,7 +260,11 @@ struct Range(B, E) # (1...10).union(1..10) # => 1..10 # ``` def union(other : Range) - return nil if self.end < other.begin || other.end < self.begin + e_s : E = self.end + e_o : E = other.end + + return if self.end < other.begin && (!e_s.responds_to?(:succ) || e_s.succ != other.begin) + return if other.end < self.begin && (!e_o.responds_to?(:succ) || e_o.succ != self.begin) larger = (self.end > other.end) Range.new( @@ -278,7 +282,7 @@ struct Range(B, E) # (1...10).intersection(7..12) # => 7...10 # ``` def intersection(other : Range) - return nil if self.end < other.begin || other.end < self.begin + return if self.end < other.begin || other.end < self.begin larger = (self.end > other.end) Range.new( From 2a3801a6c26e71ed4a8a3b033a0b104a2c83d3ce Mon Sep 17 00:00:00 2001 From: CTC97 Date: Thu, 7 Nov 2024 23:29:59 -0500 Subject: [PATCH 10/15] exclusive range checks + refined range definitions --- spec/std/range_spec.cr | 26 ++++++++++++++------------ src/range.cr | 20 +++++++++----------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index cacb92d154ff..7c834c1a1c87 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -82,18 +82,7 @@ describe "Range" do it "does union" do # Int ranges (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges - - # TODO: This test will fail - - # In an integer setting this case is doable. We can simply check adjaceny with +-1. - # However, how do we define an adjacent range for other data types? - # Examples of adjacency ambiguity: - # - Floats: Is 4.1's upper adjacent value 4.2 or 4.11, etc.? - # - Times: Is a time's adjacent value +1 second or +1 hour, etc.? - # - # How do we define adjacency? - # - (1..5).union(6..10).should eq(1..10) # Adjacent ranges - + (1..5).union(6..10).should eq(1..10) # Adjacent ranges (1..5).union(10..15).should eq(nil) # Disjoint integer ranges @@ -115,6 +104,13 @@ describe "Range" do t4 = Time.local(2024, 10, 15) (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges (t1..t2).union(t3..t4).should eq(nil) # Disjoint time ranges + + # Exclusive ranges + (1...5).union(5...10).should eq(1...10) # Adjacent exclusive integer ranges + (1.0...5.5).union(3.2...7.8).should eq(1.0...7.8) # Overlapping exclusive float ranges + ('a'...'e').union('c'...'g').should eq('a'...'g') # Overlapping exclusive string ranges + (t1...t2).union(t2...t3).should eq(t1...t3) # Adjacent exclusive time ranges + end it "does intersection" do @@ -139,6 +135,12 @@ describe "Range" do t4 = Time.local(2024, 10, 15) (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges (t1..t2).intersection(t3..t4).should eq(nil) # Non-overlapping time ranges + + # Exclusive ranges + (1...5).intersection(3...7).should eq(3...5) # Overlapping exclusive integer ranges + (1.0...5.5).intersection(3.2...7.8).should eq(3.2...5.5) # Overlapping exclusive float ranges + ('a'...'e').intersection('c'...'g').should eq('c'...'e') # Overlapping exclusive string ranges + (t1...t3).intersection(t2...t4).should eq(t2...t3) # Overlapping exclusive time ranges end it "overlaps?" do diff --git a/src/range.cr b/src/range.cr index 397d8de06836..8a0a9a219dac 100644 --- a/src/range.cr +++ b/src/range.cr @@ -253,7 +253,7 @@ struct Range(B, E) end # Returns the union of this range, and another. - # Returns 0..0 if there is no overlap. + # Returns `nil` if there is no overlap. # # ``` # (1..5).union(5..10) # => 1..10 @@ -266,16 +266,15 @@ struct Range(B, E) return if self.end < other.begin && (!e_s.responds_to?(:succ) || e_s.succ != other.begin) return if other.end < self.begin && (!e_o.responds_to?(:succ) || e_o.succ != self.begin) - larger = (self.end > other.end) Range.new( - self.begin < other.begin ? self.begin : other.begin, - self.end > other.end ? self.end : other.end, - exclusive: (larger && self.excludes_end?) || (!larger && other.excludes_end?), + Math.min(self.begin, other.begin), + Math.max(self.end, other.end), + exclusive:({self, other}.max_by(&.end).excludes_end?), ) end # Returns the intersection of this range, and another. - # Returns 0..0 if there is no overlap. + # Returns `nil` if there is no overlap. # # ``` # (2..10).intersection(0..8) # => 2..8 @@ -284,12 +283,11 @@ struct Range(B, E) def intersection(other : Range) return if self.end < other.begin || other.end < self.begin - larger = (self.end > other.end) Range.new( - self.begin > other.begin ? self.begin : other.begin, - self.end < other.end ? self.end : other.end, - exclusive:(!larger && self.excludes_end?) || (larger && other.excludes_end?), - ) + Math.max(self.begin, other.begin), + Math.min(self.end, other.end), + exclusive:({self, other}.max_by(&.end).excludes_end?), + ) end # Returns `true` if this range overlaps with another range. From 9f0e60c55ce39450acb817503044dfda39709efc Mon Sep 17 00:00:00 2001 From: CTC97 Date: Sun, 10 Nov 2024 21:38:36 -0500 Subject: [PATCH 11/15] format check fix :hammer: --- spec/std/range_spec.cr | 82 ++++++++++++++++++++---------------------- src/range.cr | 6 ++-- 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index 7c834c1a1c87..8bf08538b0cf 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -81,90 +81,86 @@ describe "Range" do it "does union" do # Int ranges - (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges - (1..5).union(6..10).should eq(1..10) # Adjacent ranges - (1..5).union(10..15).should eq(nil) # Disjoint integer ranges - + (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges + (1..5).union(6..10).should eq(1..10) # Adjacent ranges + (1..5).union(10..15).should eq(nil) # Disjoint integer ranges # Float ranges - (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges - (1.0..2.5).union(3.0..4.0).should eq(nil) # Non-overlapping float ranges - - + (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges + (1.0..2.5).union(3.0..4.0).should eq(nil) # Non-overlapping float ranges # String ranges - ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges - ('a'..'c').union('e'..'f').should eq(nil) # Non-overlapping string ranges - ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges - + ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges + ('a'..'c').union('e'..'f').should eq(nil) # Non-overlapping string ranges + ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges + # Time ranges t1 = Time.local(2024, 10, 1) t2 = Time.local(2024, 10, 5) t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) - (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges - (t1..t2).union(t3..t4).should eq(nil) # Disjoint time ranges + (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges + (t1..t2).union(t3..t4).should eq(nil) # Disjoint time ranges # Exclusive ranges - (1...5).union(5...10).should eq(1...10) # Adjacent exclusive integer ranges - (1.0...5.5).union(3.2...7.8).should eq(1.0...7.8) # Overlapping exclusive float ranges - ('a'...'e').union('c'...'g').should eq('a'...'g') # Overlapping exclusive string ranges - (t1...t2).union(t2...t3).should eq(t1...t3) # Adjacent exclusive time ranges + (1...5).union(5...10).should eq(1...10) # Adjacent exclusive integer ranges + (1.0...5.5).union(3.2...7.8).should eq(1.0...7.8) # Overlapping exclusive float ranges + ('a'...'e').union('c'...'g').should eq('a'...'g') # Overlapping exclusive string ranges + (t1...t2).union(t2...t3).should eq(t1...t3) # Adjacent exclusive time ranges end - + it "does intersection" do # Int ranges - (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges - (1..5).intersection(6..10).should eq(nil) # Non-overlapping integer ranges + (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges + (1..5).intersection(6..10).should eq(nil) # Non-overlapping integer ranges (1..5).intersection(5..10).should eq(5..5) - #(1...1).intersection(0..10).should eq(nil) + # (1...1).intersection(0..10).should eq(nil) # Float ranges - (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges - (1.0..2.5).intersection(3.0..4.0).should eq(nil) # Non-overlapping float ranges - + (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges + (1.0..2.5).intersection(3.0..4.0).should eq(nil) # Non-overlapping float ranges + # String ranges - ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges - ('a'..'c').intersection('d'..'f').should eq(nil) # Non-overlapping string ranges - + ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges + ('a'..'c').intersection('d'..'f').should eq(nil) # Non-overlapping string ranges + # Time ranges t1 = Time.local(2024, 10, 1) t2 = Time.local(2024, 10, 5) t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) - (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges - (t1..t2).intersection(t3..t4).should eq(nil) # Non-overlapping time ranges + (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges + (t1..t2).intersection(t3..t4).should eq(nil) # Non-overlapping time ranges # Exclusive ranges - (1...5).intersection(3...7).should eq(3...5) # Overlapping exclusive integer ranges - (1.0...5.5).intersection(3.2...7.8).should eq(3.2...5.5) # Overlapping exclusive float ranges - ('a'...'e').intersection('c'...'g').should eq('c'...'e') # Overlapping exclusive string ranges - (t1...t3).intersection(t2...t4).should eq(t2...t3) # Overlapping exclusive time ranges + (1...5).intersection(3...7).should eq(3...5) # Overlapping exclusive integer ranges + (1.0...5.5).intersection(3.2...7.8).should eq(3.2...5.5) # Overlapping exclusive float ranges + ('a'...'e').intersection('c'...'g').should eq('c'...'e') # Overlapping exclusive string ranges + (t1...t3).intersection(t2...t4).should eq(t2...t3) # Overlapping exclusive time ranges end - + it "overlaps?" do # Int ranges - (1..5).overlaps?(3..7).should eq(true) # Overlapping integer ranges - (1..5).overlaps?(6..10).should eq(false) # Non-overlapping integer ranges - + (1..5).overlaps?(3..7).should eq(true) # Overlapping integer ranges + (1..5).overlaps?(6..10).should eq(false) # Non-overlapping integer ranges + # Float ranges (1.0..5.5).overlaps?(3.2..7.8).should eq(true) # Overlapping float ranges (1.0..2.5).overlaps?(3.0..4.0).should eq(false) # Non-overlapping float ranges - + # String ranges ('a'..'e').overlaps?('c'..'g').should eq(true) # Overlapping string ranges ('a'..'c').overlaps?('d'..'f').should eq(false) # Non-overlapping string ranges - + # Time ranges t1 = Time.local(2024, 10, 1) t2 = Time.local(2024, 10, 5) t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) - (t1..t3).overlaps?(t2..t4).should eq(true) # Overlapping time ranges - (t1..t2).overlaps?(t3..t4).should eq(false) # Non-overlapping time ranges + (t1..t3).overlaps?(t2..t4).should eq(true) # Overlapping time ranges + (t1..t2).overlaps?(t3..t4).should eq(false) # Non-overlapping time ranges end - it "does to_s" do (1...5).to_s.should eq("1...5") diff --git a/src/range.cr b/src/range.cr index 8a0a9a219dac..7b053d7b56a2 100644 --- a/src/range.cr +++ b/src/range.cr @@ -269,7 +269,7 @@ struct Range(B, E) Range.new( Math.min(self.begin, other.begin), Math.max(self.end, other.end), - exclusive:({self, other}.max_by(&.end).excludes_end?), + exclusive: ({self, other}.max_by(&.end).excludes_end?), ) end @@ -286,8 +286,8 @@ struct Range(B, E) Range.new( Math.max(self.begin, other.begin), Math.min(self.end, other.end), - exclusive:({self, other}.max_by(&.end).excludes_end?), - ) + exclusive: ({self, other}.max_by(&.end).excludes_end?), + ) end # Returns `true` if this range overlaps with another range. From 771e3e4250a7a4427b45dd5d8eb26849f5dde4c0 Mon Sep 17 00:00:00 2001 From: CTC97 Date: Mon, 11 Nov 2024 21:37:22 -0500 Subject: [PATCH 12/15] nit. --- src/range.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/range.cr b/src/range.cr index 7b053d7b56a2..e50e20ea4a17 100644 --- a/src/range.cr +++ b/src/range.cr @@ -269,7 +269,7 @@ struct Range(B, E) Range.new( Math.min(self.begin, other.begin), Math.max(self.end, other.end), - exclusive: ({self, other}.max_by(&.end).excludes_end?), + exclusive: {self, other}.max_by(&.end).excludes_end?, ) end @@ -286,7 +286,7 @@ struct Range(B, E) Range.new( Math.max(self.begin, other.begin), Math.min(self.end, other.end), - exclusive: ({self, other}.max_by(&.end).excludes_end?), + exclusive: {self, other}.max_by(&.end).excludes_end?, ) end From 67ebd95d17a5fcebbbc15fff56852231227e032a Mon Sep 17 00:00:00 2001 From: Connor Date: Thu, 5 Dec 2024 21:31:45 -0500 Subject: [PATCH 13/15] addressed empty ranges + updated spec --- spec/std/range_spec.cr | 18 +++++++++--------- src/range.cr | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index 8bf08538b0cf..d1c6b0f99f02 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -83,15 +83,15 @@ describe "Range" do # Int ranges (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges (1..5).union(6..10).should eq(1..10) # Adjacent ranges - (1..5).union(10..15).should eq(nil) # Disjoint integer ranges + (1..5).union(10..15).should eq(1...1) # Disjoint integer ranges # Float ranges (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges - (1.0..2.5).union(3.0..4.0).should eq(nil) # Non-overlapping float ranges + (1.0..2.5).union(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges # String ranges ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges - ('a'..'c').union('e'..'f').should eq(nil) # Non-overlapping string ranges + ('a'..'c').union('e'..'f').should eq('a'...'a') # Non-overlapping string ranges ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges # Time ranges @@ -100,7 +100,7 @@ describe "Range" do t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges - (t1..t2).union(t3..t4).should eq(nil) # Disjoint time ranges + (t1..t2).union(t3..t4).should eq(t1...t1) # Disjoint time ranges # Exclusive ranges (1...5).union(5...10).should eq(1...10) # Adjacent exclusive integer ranges @@ -113,17 +113,17 @@ describe "Range" do it "does intersection" do # Int ranges (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges - (1..5).intersection(6..10).should eq(nil) # Non-overlapping integer ranges + (1..5).intersection(6..10).should eq(1...1) # Non-overlapping integer ranges (1..5).intersection(5..10).should eq(5..5) - # (1...1).intersection(0..10).should eq(nil) + (1...1).intersection(0..10).should eq(1...1) # Float ranges (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges - (1.0..2.5).intersection(3.0..4.0).should eq(nil) # Non-overlapping float ranges + (1.0..2.5).intersection(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges # String ranges ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges - ('a'..'c').intersection('d'..'f').should eq(nil) # Non-overlapping string ranges + ('a'..'c').intersection('d'..'f').should eq('a'...'a') # Non-overlapping string ranges # Time ranges t1 = Time.local(2024, 10, 1) @@ -131,7 +131,7 @@ describe "Range" do t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges - (t1..t2).intersection(t3..t4).should eq(nil) # Non-overlapping time ranges + (t1..t2).intersection(t3..t4).should eq(t1...t1) # Non-overlapping time ranges # Exclusive ranges (1...5).intersection(3...7).should eq(3...5) # Overlapping exclusive integer ranges diff --git a/src/range.cr b/src/range.cr index e50e20ea4a17..98cd1bae77a9 100644 --- a/src/range.cr +++ b/src/range.cr @@ -263,13 +263,13 @@ struct Range(B, E) e_s : E = self.end e_o : E = other.end - return if self.end < other.begin && (!e_s.responds_to?(:succ) || e_s.succ != other.begin) - return if other.end < self.begin && (!e_o.responds_to?(:succ) || e_o.succ != self.begin) + return Range.new(self.begin, self.begin, exclusive: true) if self.end < other.begin && (!e_s.responds_to?(:succ) || e_s.succ != other.begin) + return Range.new(self.begin, self.begin, exclusive: true) if other.end < self.begin && (!e_o.responds_to?(:succ) || e_o.succ != self.begin) Range.new( Math.min(self.begin, other.begin), Math.max(self.end, other.end), - exclusive: {self, other}.max_by(&.end).excludes_end?, + exclusive: self.excludes_end? || other.excludes_end?, ) end @@ -281,12 +281,12 @@ struct Range(B, E) # (1...10).intersection(7..12) # => 7...10 # ``` def intersection(other : Range) - return if self.end < other.begin || other.end < self.begin + return Range.new(self.begin, self.begin, exclusive: true) if self.end < other.begin || other.end < self.begin Range.new( Math.max(self.begin, other.begin), Math.min(self.end, other.end), - exclusive: {self, other}.max_by(&.end).excludes_end?, + exclusive: self.excludes_end? || other.excludes_end?, ) end From d4e35c1eb7d09a8fad9e688a6dfc1e111128cc2d Mon Sep 17 00:00:00 2001 From: Connor Date: Thu, 5 Dec 2024 21:35:36 -0500 Subject: [PATCH 14/15] format + logic fix --- spec/std/range_spec.cr | 34 +++++++++++++++++----------------- src/range.cr | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/std/range_spec.cr b/spec/std/range_spec.cr index d1c6b0f99f02..dfa7802e788a 100644 --- a/spec/std/range_spec.cr +++ b/spec/std/range_spec.cr @@ -81,26 +81,26 @@ describe "Range" do it "does union" do # Int ranges - (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges - (1..5).union(6..10).should eq(1..10) # Adjacent ranges - (1..5).union(10..15).should eq(1...1) # Disjoint integer ranges + (1..5).union(3..7).should eq(1..7) # Overlapping integer ranges + (1..5).union(6..10).should eq(1..10) # Adjacent ranges + (1..5).union(10..15).should eq(1...1) # Disjoint integer ranges # Float ranges - (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges - (1.0..2.5).union(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges + (1.0..5.5).union(3.2..7.8).should eq(1.0..7.8) # Overlapping float ranges + (1.0..2.5).union(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges # String ranges - ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges - ('a'..'c').union('e'..'f').should eq('a'...'a') # Non-overlapping string ranges - ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges + ('a'..'e').union('c'..'g').should eq('a'..'g') # Overlapping string ranges + ('a'..'c').union('e'..'f').should eq('a'...'a') # Non-overlapping string ranges + ('a'..'c').union('d'..'f').should eq('a'..'f') # Adjacent string ranges # Time ranges t1 = Time.local(2024, 10, 1) t2 = Time.local(2024, 10, 5) t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) - (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges - (t1..t2).union(t3..t4).should eq(t1...t1) # Disjoint time ranges + (t1..t2).union(t2..t3).should eq(t1..t3) # Adjacent time ranges + (t1..t2).union(t3..t4).should eq(t1...t1) # Disjoint time ranges # Exclusive ranges (1...5).union(5...10).should eq(1...10) # Adjacent exclusive integer ranges @@ -112,26 +112,26 @@ describe "Range" do it "does intersection" do # Int ranges - (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges + (1..5).intersection(3..7).should eq(3..5) # Overlapping integer ranges (1..5).intersection(6..10).should eq(1...1) # Non-overlapping integer ranges (1..5).intersection(5..10).should eq(5..5) (1...1).intersection(0..10).should eq(1...1) # Float ranges - (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges - (1.0..2.5).intersection(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges + (1.0..5.5).intersection(3.2..7.8).should eq(3.2..5.5) # Overlapping float ranges + (1.0..2.5).intersection(3.0..4.0).should eq(1.0...1.0) # Non-overlapping float ranges # String ranges - ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges - ('a'..'c').intersection('d'..'f').should eq('a'...'a') # Non-overlapping string ranges + ('a'..'e').intersection('c'..'g').should eq('c'..'e') # Overlapping string ranges + ('a'..'c').intersection('d'..'f').should eq('a'...'a') # Non-overlapping string ranges # Time ranges t1 = Time.local(2024, 10, 1) t2 = Time.local(2024, 10, 5) t3 = Time.local(2024, 10, 10) t4 = Time.local(2024, 10, 15) - (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges - (t1..t2).intersection(t3..t4).should eq(t1...t1) # Non-overlapping time ranges + (t1..t3).intersection(t2..t4).should eq(t2..t3) # Overlapping time ranges + (t1..t2).intersection(t3..t4).should eq(t1...t1) # Non-overlapping time ranges # Exclusive ranges (1...5).intersection(3...7).should eq(3...5) # Overlapping exclusive integer ranges diff --git a/src/range.cr b/src/range.cr index 98cd1bae77a9..d20cf9d6fc59 100644 --- a/src/range.cr +++ b/src/range.cr @@ -269,7 +269,7 @@ struct Range(B, E) Range.new( Math.min(self.begin, other.begin), Math.max(self.end, other.end), - exclusive: self.excludes_end? || other.excludes_end?, + {self, other}.max_by(&.end).excludes_end? ) end From e4221d1f17a6027bb3f712dac1ba95dde6028b0a Mon Sep 17 00:00:00 2001 From: Connor Date: Fri, 6 Dec 2024 22:48:32 -0500 Subject: [PATCH 15/15] docs update --- src/range.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/range.cr b/src/range.cr index d20cf9d6fc59..71a21c65e5dd 100644 --- a/src/range.cr +++ b/src/range.cr @@ -253,7 +253,7 @@ struct Range(B, E) end # Returns the union of this range, and another. - # Returns `nil` if there is no overlap. + # Returns `self.begin...self.begin` if there is no overlap. # # ``` # (1..5).union(5..10) # => 1..10 @@ -274,7 +274,7 @@ struct Range(B, E) end # Returns the intersection of this range, and another. - # Returns `nil` if there is no overlap. + # Returns `self.begin...self.begin` if there is no overlap. # # ``` # (2..10).intersection(0..8) # => 2..8