make it easier to work with single RangeInclusive<T> values#21
make it easier to work with single RangeInclusive<T> values#21CarlKCarlK merged 4 commits intoCarlKCarlK:mainfrom
Conversation
- Added a `From<RangeInclusive<T>>` constructor - Implemented `SortedStarts<T>` and `SortedDisjoint<T>` for `Option<T>::IntoIter` These changes make interfacing with single range values easier. It's especially nice when you want to convert a range into a RangeSetBlaze, or when you want to perform a set operation (i.e. &) between a RangeSetBlaze and a single Range. Please let me know if I totally missed existing support for this functionality. Cheers and thanks for the excellent library!
|
Thanks for the PR. This looks like a very useful addition! I see one issue: inclusive ranges can be empty (for example let a = RangeSetBlaze::from_iter([0..=10]);
let e = 5..=4; // empty
assert!(( &a & e ).is_empty());
assert_eq!(&a | e, a);
assert_eq!(&a - e, a);
assert_eq!(&a ^ e, a);
assert!(RangeSetBlaze::<i32>::from(5..=4).is_empty());In other words, empty ranges should be treated as the canonical empty iterator rather than a single invalid item. |
|
Thanks for the quick feedback! While fixing/testing this I also noticed similar behavior with #[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic(expected = "start must be less or equal to end")]
fn test_from_sorted_disjoint_empty_array() {
RangeSetBlaze::from_sorted_disjoint(CheckSortedDisjoint::new([6..=5]));
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic(expected = "start <= end")]
fn test_from_sorted_disjoint_empty_option() {
RangeSetBlaze::from_sorted_disjoint(Some(6..=5).into_iter());
} |
|
Carl, I’m a bit concerned that Some(5..=3).into_iter() isn’t sound, but maybe we could take it as inspiration for a small iterator struct instead? (not tested) /// A zero-allocation iterator that yields **0 or 1** sorted, disjoint, non-empty ranges.
///
/// If `start <= end`, it yields exactly one `RangeInclusive<T>` representing `start..=end`.
/// Otherwise (if `start > end`), it yields nothing — a sound representation of the empty set.
///
/// This provides a simple and efficient way to create temporary `SortedDisjoint` iterators
/// without wrapping arrays or using `Some(..).into_iter()`.
#[derive(Clone, Copy, Debug)]
pub struct SortedDisjoint01<T: Integer> {
start: T,
end: T,
}
impl<T: Integer> SortedDisjoint01<T> {
/// Creates a new adapter that will yield one range if `start <= end`, or none otherwise.
#[inline]
pub const fn new(start: T, end: T) -> Self {
Self { start, end }
}
}
impl<T: Integer> IntoIterator for SortedDisjoint01<T> {
type Item = RangeInclusive<T>;
type IntoIter = core::option::IntoIter<RangeInclusive<T>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
if self.start <= self.end {
Some(self.start..=self.end).into_iter()
} else {
None.into_iter()
}
}
}
/// Sound: always produces either 0 valid ranges, or 1 non-empty range.
/// There are never overlapping or reversed ranges.
impl<T: Integer> SortedStarts<T> for SortedDisjoint01<T> {}
impl<T: Integer> SortedDisjoint<T> for SortedDisjoint01<T> {}Examples (not tested) // Create a checked, multi-range sorted-disjoint sequence
let a = CheckSortedDisjoint::new([10..=20, 30..=40]);
// Intersection (&)
let inter = &a & SortedDisjoint01::new(15, 35);
assert!(inter.equal(CheckSortedDisjoint::new([15..=20, 30..=35])));
// Union (|)
let uni = &a | SortedDisjoint01::new(22, 25);
assert!(uni.equal(CheckSortedDisjoint::new([10..=20, 22..=25, 30..=40])));
// Difference (-)
let diff = &a - SortedDisjoint01::new(18, 32);
assert!(diff.equal(CheckSortedDisjoint::new([10..=17, 33..=40])));
// Symmetric Difference (^)
let sym = &a ^ SortedDisjoint01::new(18, 32);
assert!(sym.equal(CheckSortedDisjoint::new([10..=17, 21..=25, 33..=40])));
// Empty RHS (start > end)
let empty = &a & SortedDisjoint01::new(5, 3);
assert!(empty.equal(CheckSortedDisjoint::new([])));Do you think this fits your intent? If you’re happy with it, you’re welcome to add it to the PR. |
|
I'm fine with a separate wrapper optimized for single ranges if you'd prefer that. However, I'm not sure |
|
I updated the implementation and PR description to make things cleaner. I also added docs. I really like this new solution. It feels right. I appreciate your patience as we iterate! |
|
@CarlKCarlK I just saw the failing tests. Will address clippy issues I added + fix the wasm issue. |
|
Thanks!
From: Carl Sverre ***@***.***>
Sent: Sunday, October 26, 2025 3:47 PM
To: CarlKCarlK/range-set-blaze ***@***.***>
Cc: Carl Kadie ***@***.***>; Mention ***@***.***>
Subject: Re: [CarlKCarlK/range-set-blaze] make it easier to work with single RangeInclusive<T> values (PR #21)
[https://avatars.githubusercontent.com/u/82591?s=20&v=4]carlsverre left a comment (CarlKCarlK/range-set-blaze#21)<#21 (comment)>
@CarlKCarlK<https://github.com/CarlKCarlK> I just saw the failing tests. Will address clippy issues I added + fix the wasm issue.
-
Reply to this email directly, view it on GitHub<#21 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/ABR65P2XFUZXMMLHUV7S65L3ZVFNHAVCNFSM6AAAAACJ6MIWJSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTINBYHE4DEOBRG4>.
You are receiving this because you were mentioned.Message ID: ***@***.******@***.***>>
|
|
Fixed! (I haven't got the wasm tests running fully locally yet, so hopefully my fix addresses the issue) |
|
Closed and published with 0.4.1. |
From<RangeInclusive<T>>constructorRangeOnce<T>, which is analogous tostd::iter::Once(RangeInclusive<T>)but modified to treat an empty range as an empty iterator.These changes make interfacing with single range values easier. It's especially nice when you want to convert a range into a RangeSetBlaze, or when you want to perform a set operation (i.e., &) between a RangeSetBlaze and a single Range.