philiprehberger-interval 0.4.0 → 0.6.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +30 -0
- data/lib/philiprehberger/interval/range.rb +57 -0
- data/lib/philiprehberger/interval/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8aaad786d674299bcc97c96acb85df9aad7bcd91b3794404a6aad674946c8461
|
|
4
|
+
data.tar.gz: 1ec309715dd29b1f132481150d32a1c00d95413d3c0ad3aca1d6ea79420daa0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34ff8595dd0ede63f6cc187e06ba0de8a51fb80d15d777845c72512f8721402e225e54dbefc56089eb44610549e6de4307e03c2503a133cd9844e8446c5dbe80
|
|
7
|
+
data.tar.gz: 27c3dc2e5c9215554f995de13fbed3090311f0f09e337ea1b8d100d68e526731de1d089b141dbcb2c938421b1a8a5dd9ada07d95d3d9b123aaa72a0ab1b70f6e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-04-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Range#touching?` — strict boundary-touching predicate
|
|
14
|
+
- `Range#length` — alias for `#size`
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- `bug_report.yml` — require Ruby version; add Gem version input per guide
|
|
18
|
+
|
|
19
|
+
## [0.5.0] - 2026-04-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- `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
|
|
23
|
+
|
|
10
24
|
## [0.4.0] - 2026-04-15
|
|
11
25
|
|
|
12
26
|
### Added
|
data/README.md
CHANGED
|
@@ -84,6 +84,19 @@ Philiprehberger::Interval.gaps(intervals)
|
|
|
84
84
|
# => [[7, 10]]
|
|
85
85
|
```
|
|
86
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
|
+
|
|
87
100
|
### Finding a Common Intersection
|
|
88
101
|
|
|
89
102
|
Compute the overlap shared by every interval, or `nil` if any pair is disjoint:
|
|
@@ -97,6 +110,20 @@ Philiprehberger::Interval.intersection([
|
|
|
97
110
|
# => [5, 8]
|
|
98
111
|
```
|
|
99
112
|
|
|
113
|
+
### Touching Intervals
|
|
114
|
+
|
|
115
|
+
Check whether two intervals meet at exactly one point with no overlap and no gap — useful when partitioning a range so every value is covered exactly once:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
a = Philiprehberger::Interval.new(1, 5, type: :right_open) # [1, 5)
|
|
119
|
+
b = Philiprehberger::Interval.new(5, 10, type: :closed) # [5, 10]
|
|
120
|
+
a.touching?(b) # => true (5 covered once)
|
|
121
|
+
|
|
122
|
+
c = Philiprehberger::Interval.new(1, 5, type: :closed) # [1, 5]
|
|
123
|
+
d = Philiprehberger::Interval.new(5, 10, type: :closed) # [5, 10]
|
|
124
|
+
c.touching?(d) # => false (5 covered twice)
|
|
125
|
+
```
|
|
126
|
+
|
|
100
127
|
### With Time Values
|
|
101
128
|
|
|
102
129
|
```ruby
|
|
@@ -113,16 +140,19 @@ shift.include?(Time.new(2026, 1, 1, 12)) # => true
|
|
|
113
140
|
| `#overlaps?(other)` | Check if two intervals overlap (respects boundary types) |
|
|
114
141
|
| `#contains?(other)` | Check if interval fully contains another |
|
|
115
142
|
| `#adjacent?(other)` | Check if intervals are touching but not overlapping |
|
|
143
|
+
| `#touching?(other)` | Check if intervals meet at a single point with exactly one closed side |
|
|
116
144
|
| `#intersect(other)` | Return the overlap between two intervals |
|
|
117
145
|
| `#overlap_ratio(other)` | Fraction of self covered by other (0.0–1.0) |
|
|
118
146
|
| `#union(other)` | Return the combined interval |
|
|
119
147
|
| `#subtract(other)` | Remove another interval, returning remaining parts |
|
|
120
148
|
| `#size` | Length of the interval |
|
|
149
|
+
| `#length` | Alias for `#size` |
|
|
121
150
|
| `#include?(point)` | Check if a point falls within the interval (respects boundary types) |
|
|
122
151
|
| `#shift(delta)` | Return new interval shifted by delta, preserving type |
|
|
123
152
|
| `#scale(factor, anchor:)` | Scale width around anchor (`:center`, `:left`, `:right`) |
|
|
124
153
|
| `#split(n)` | Split into n equal sub-intervals |
|
|
125
154
|
| `#clamp(value)` | Clamp value to interval bounds |
|
|
155
|
+
| `#sample(n = nil)` | Return a random Float, or array of n Floats, within the interval (respects boundaries) |
|
|
126
156
|
| `.merge(intervals)` | Merge overlapping intervals into non-overlapping set |
|
|
127
157
|
| `.gaps(intervals)` | Find gaps between a set of intervals |
|
|
128
158
|
| `.intersection(intervals)` | Common overlap of a collection of intervals, or `nil` |
|
|
@@ -112,6 +112,27 @@ module Philiprehberger
|
|
|
112
112
|
def size
|
|
113
113
|
@finish - @start
|
|
114
114
|
end
|
|
115
|
+
alias length size
|
|
116
|
+
|
|
117
|
+
# Check if this interval strictly touches another at a single endpoint.
|
|
118
|
+
#
|
|
119
|
+
# Returns +true+ iff the two intervals share exactly one endpoint value
|
|
120
|
+
# and exactly one of the two boundaries at that point is closed — so the
|
|
121
|
+
# meeting point is covered exactly once, with no gap and no overlap.
|
|
122
|
+
#
|
|
123
|
+
# @param other [Range] the other interval
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def touching?(other)
|
|
126
|
+
return false if overlaps?(other)
|
|
127
|
+
|
|
128
|
+
if @finish == other.start
|
|
129
|
+
right_closed? ^ other.left_closed?
|
|
130
|
+
elsif other.finish == @start
|
|
131
|
+
other.right_closed? ^ left_closed?
|
|
132
|
+
else
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
115
136
|
|
|
116
137
|
# Return the fraction of self that is covered by another interval.
|
|
117
138
|
#
|
|
@@ -186,6 +207,26 @@ module Philiprehberger
|
|
|
186
207
|
end
|
|
187
208
|
end
|
|
188
209
|
|
|
210
|
+
# Return one or more random Floats within the interval.
|
|
211
|
+
#
|
|
212
|
+
# Without an argument, returns a single random Float. With an integer
|
|
213
|
+
# argument, returns an array of that many random Floats. Respects boundary
|
|
214
|
+
# types: open boundaries are excluded via rejection sampling. Returns
|
|
215
|
+
# +start+ immediately for point intervals (start == finish, closed).
|
|
216
|
+
#
|
|
217
|
+
# @param n [Integer, nil] number of samples, or nil for a single value
|
|
218
|
+
# @return [Float, Array<Float>]
|
|
219
|
+
# @raise [Error] if the interval is empty
|
|
220
|
+
def sample(n = nil)
|
|
221
|
+
raise Error, 'cannot sample an empty interval' if empty?
|
|
222
|
+
|
|
223
|
+
if n.nil?
|
|
224
|
+
sample_one
|
|
225
|
+
else
|
|
226
|
+
Array.new(n) { sample_one }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
189
230
|
# Clamp a value to the interval bounds.
|
|
190
231
|
#
|
|
191
232
|
# @param value [Comparable] the value to clamp
|
|
@@ -239,6 +280,22 @@ module Philiprehberger
|
|
|
239
280
|
def empty?
|
|
240
281
|
@start == @finish && @type != :closed
|
|
241
282
|
end
|
|
283
|
+
|
|
284
|
+
private
|
|
285
|
+
|
|
286
|
+
def sample_one
|
|
287
|
+
return @start.to_f if @start == @finish
|
|
288
|
+
|
|
289
|
+
lo = @start.to_f
|
|
290
|
+
hi = @finish.to_f
|
|
291
|
+
|
|
292
|
+
loop do
|
|
293
|
+
value = lo + (rand * (hi - lo))
|
|
294
|
+
left_ok = left_closed? ? value >= lo : value > lo
|
|
295
|
+
right_ok = right_closed? ? value <= hi : value < hi
|
|
296
|
+
return value if left_ok && right_ok
|
|
297
|
+
end
|
|
298
|
+
end
|
|
242
299
|
end
|
|
243
300
|
end
|
|
244
301
|
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.
|
|
4
|
+
version: 0.6.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-
|
|
11
|
+
date: 2026-04-21 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,
|