philiprehberger-interval 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2614076496fada8f6202df687ec993c5438e3a47945824e05e6bb4d00cc1c046
4
- data.tar.gz: 2557c885218f82fe7de28ea6e91e660b9b901ab2995ca8d68e3a1ef9cf9b2d21
3
+ metadata.gz: 2cc42dea01af0c3d94ccd1473f57540edadacd0da1763cfb29cfcceaca105900
4
+ data.tar.gz: 0455405d87eda7c2a20b913bcd9d26477247dfd69a99adc2f00be29c340aaf5e
5
5
  SHA512:
6
- metadata.gz: ed05a860c2d28ce9cfdf00787585e117c41ee03bd1a4f917f4c06d4c461313a7fcdfe0fa7ce9f6073804c4c67c1169e84a7988a2070d8f54f42a725a0caef36b
7
- data.tar.gz: 8451b55300a3d8d489cc895792256f4e7a1077d9be2a0df25aee1e7f9ef42d9defa2de2fe7eabe3b3d3c58c6c959037f65f89a06eaa8e96d61025ff91a09e285
6
+ metadata.gz: fd54e350ee506ed02827ad6c23f4eec3d492eadc9b68cee0d4a4608773e1611a799ceb4ca2e56409ba0e27145852fe5c6d8adb168875f93c3818f25e82ef4c55
7
+ data.tar.gz: 626544c2d371d2c1de5a1a1b4f4ac7f181af2b8397ae85168e617b7d77a8445f0f3f6d7e66a01fc81586bce8f144692410d8c0a677d86cffbf44b323bfc4498a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-04-16
11
+
12
+ ### Added
13
+ - `Range#sample(n = nil)` — return a random Float within the interval, or an array of n random Floats; respects open/closed boundaries via rejection sampling; raises on empty intervals; returns start for point intervals
14
+
15
+ ## [0.4.0] - 2026-04-15
16
+
17
+ ### Added
18
+ - `Range#overlap_ratio(other)` — fraction of self covered by another interval (Float in 0.0–1.0)
19
+
10
20
  ## [0.3.0] - 2026-04-15
11
21
 
12
22
  ### Added
data/README.md CHANGED
@@ -36,6 +36,7 @@ a.overlaps?(b) # => true
36
36
  a.intersect(b) # => [3, 5]
37
37
  a.union(b) # => [1, 8]
38
38
  a.include?(4) # => true
39
+ a.overlap_ratio(b) # => 0.5
39
40
  ```
40
41
 
41
42
  ### Interval Types
@@ -83,6 +84,19 @@ Philiprehberger::Interval.gaps(intervals)
83
84
  # => [[7, 10]]
84
85
  ```
85
86
 
87
+ ### Sampling Random Values
88
+
89
+ Return one or more random Floats from within an interval. Open boundaries are respected via rejection sampling:
90
+
91
+ ```ruby
92
+ interval = Philiprehberger::Interval.new(1.0, 5.0)
93
+ interval.sample # => 3.247... (single Float)
94
+ interval.sample(3) # => [1.823..., 4.501..., 2.964...]
95
+
96
+ open = Philiprehberger::Interval.new(1.0, 5.0, type: :open)
97
+ open.sample # => always strictly between 1.0 and 5.0
98
+ ```
99
+
86
100
  ### Finding a Common Intersection
87
101
 
88
102
  Compute the overlap shared by every interval, or `nil` if any pair is disjoint:
@@ -113,6 +127,7 @@ shift.include?(Time.new(2026, 1, 1, 12)) # => true
113
127
  | `#contains?(other)` | Check if interval fully contains another |
114
128
  | `#adjacent?(other)` | Check if intervals are touching but not overlapping |
115
129
  | `#intersect(other)` | Return the overlap between two intervals |
130
+ | `#overlap_ratio(other)` | Fraction of self covered by other (0.0–1.0) |
116
131
  | `#union(other)` | Return the combined interval |
117
132
  | `#subtract(other)` | Remove another interval, returning remaining parts |
118
133
  | `#size` | Length of the interval |
@@ -121,6 +136,7 @@ shift.include?(Time.new(2026, 1, 1, 12)) # => true
121
136
  | `#scale(factor, anchor:)` | Scale width around anchor (`:center`, `:left`, `:right`) |
122
137
  | `#split(n)` | Split into n equal sub-intervals |
123
138
  | `#clamp(value)` | Clamp value to interval bounds |
139
+ | `#sample(n = nil)` | Return a random Float, or array of n Floats, within the interval (respects boundaries) |
124
140
  | `.merge(intervals)` | Merge overlapping intervals into non-overlapping set |
125
141
  | `.gaps(intervals)` | Find gaps between a set of intervals |
126
142
  | `.intersection(intervals)` | Common overlap of a collection of intervals, or `nil` |
@@ -113,6 +113,23 @@ module Philiprehberger
113
113
  @finish - @start
114
114
  end
115
115
 
116
+ # Return the fraction of self that is covered by another interval.
117
+ #
118
+ # Returns a Float in 0.0..1.0. Returns 0.0 if disjoint, 1.0 if self is
119
+ # fully contained in other. If self has zero length (a point), returns
120
+ # 1.0 when the point is within other, else 0.0.
121
+ #
122
+ # @param other [Range] the other interval
123
+ # @return [Float] the fraction of self covered by other
124
+ def overlap_ratio(other)
125
+ return other.include?(@start) ? 1.0 : 0.0 if size.zero?
126
+
127
+ overlap = intersect(other)
128
+ return 0.0 if overlap.nil?
129
+
130
+ overlap.size.to_f / size
131
+ end
132
+
116
133
  # Check if a point is within the interval.
117
134
  #
118
135
  # @param point [Comparable] the point to check
@@ -169,6 +186,26 @@ module Philiprehberger
169
186
  end
170
187
  end
171
188
 
189
+ # Return one or more random Floats within the interval.
190
+ #
191
+ # Without an argument, returns a single random Float. With an integer
192
+ # argument, returns an array of that many random Floats. Respects boundary
193
+ # types: open boundaries are excluded via rejection sampling. Returns
194
+ # +start+ immediately for point intervals (start == finish, closed).
195
+ #
196
+ # @param n [Integer, nil] number of samples, or nil for a single value
197
+ # @return [Float, Array<Float>]
198
+ # @raise [Error] if the interval is empty
199
+ def sample(n = nil)
200
+ raise Error, 'cannot sample an empty interval' if empty?
201
+
202
+ if n.nil?
203
+ sample_one
204
+ else
205
+ Array.new(n) { sample_one }
206
+ end
207
+ end
208
+
172
209
  # Clamp a value to the interval bounds.
173
210
  #
174
211
  # @param value [Comparable] the value to clamp
@@ -222,6 +259,22 @@ module Philiprehberger
222
259
  def empty?
223
260
  @start == @finish && @type != :closed
224
261
  end
262
+
263
+ private
264
+
265
+ def sample_one
266
+ return @start.to_f if @start == @finish
267
+
268
+ lo = @start.to_f
269
+ hi = @finish.to_f
270
+
271
+ loop do
272
+ value = lo + (rand * (hi - lo))
273
+ left_ok = left_closed? ? value >= lo : value > lo
274
+ right_ok = right_closed? ? value <= hi : value < hi
275
+ return value if left_ok && right_ok
276
+ end
277
+ end
225
278
  end
226
279
  end
227
280
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module Interval
5
- VERSION = '0.3.0'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-interval
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-15 00:00:00.000000000 Z
11
+ date: 2026-04-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Interval data type supporting closed, open, and half-open boundaries
14
14
  with overlap detection, containment, intersection, union, subtraction, shift, scale,