Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub use crate::set::demo_read_ranges_from_file;
pub use crate::set::{IntoIter, Iter, RangeSetBlaze};

mod sorted_disjoint;
pub use sorted_disjoint::{CheckSortedDisjoint, SortedDisjoint, SortedStarts};
pub use sorted_disjoint::{CheckSortedDisjoint, RangeOnce, SortedDisjoint, SortedStarts};

mod sorted_disjoint_map;
pub use sorted_disjoint_map::{
Expand Down
10 changes: 10 additions & 0 deletions src/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::{
};

use crate::alloc::string::ToString;
use crate::sorted_disjoint::RangeOnce;
use alloc::collections::{BTreeMap, btree_map};
use alloc::string::String;
use alloc::vec::Vec;
Expand Down Expand Up @@ -89,6 +90,7 @@ where
/// | [`from_slice`][5] | slice of integers | Fast, but nightly-only |
/// | [`from_sorted_disjoint`][3]/[`into_range_set_blaze`][3] | [`SortedDisjoint`] iterator | |
/// | [`from`][5] /[`into`][5] | array of integers | |
/// | [`from`][7] | `RangeInclusive<T>` | |
///
///
/// [`BTreeMap`]: alloc::collections::BTreeMap
Expand All @@ -100,6 +102,7 @@ where
/// [SortedDisjoint]: crate::SortedDisjoint.html#table-of-contents
/// [5]: RangeSetBlaze::from
/// [6]: RangeSetBlaze::from_slice()
/// [7]: #method.from-1
///
/// # Constructor Performance
///
Expand Down Expand Up @@ -1418,6 +1421,13 @@ impl<T: Integer, const N: usize> From<[T; N]> for RangeSetBlaze<T> {
}
}

impl<T: Integer> From<RangeInclusive<T>> for RangeSetBlaze<T> {
/// Construct a [`RangeSetBlaze<T>`] directly from a [`RangeInclusive<T>`].
fn from(value: RangeInclusive<T>) -> Self {
Self::from_sorted_disjoint(RangeOnce::new(value))
}
}

gen_ops_ex!(
<T>;
types ref RangeSetBlaze<T>, ref RangeSetBlaze<T> => RangeSetBlaze<T>;
Expand Down
67 changes: 64 additions & 3 deletions src/sorted_disjoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub trait SortedStarts<T: Integer>: Iterator<Item = RangeInclusive<T>> + FusedIt
/// | [`RangeSetBlaze`] | [`ranges`] |
/// | [`RangeSetBlaze`] | [`into_ranges`] |
/// | sorted & disjoint ranges | [`CheckSortedDisjoint::new`] |
/// | [`RangeInclusive`] | [`RangeOnce::new`] |
/// | *your iterator type* | *[How to mark your type as `SortedDisjoint`][1]* |
///
/// [`ranges`]: RangeSetBlaze::ranges
Expand Down Expand Up @@ -76,7 +77,7 @@ pub trait SortedStarts<T: Integer>: Iterator<Item = RangeInclusive<T>> + FusedIt
///
/// | Set Operators | Operator | Multiway (same type) | Multiway (different types) |
/// |------------------------------------|-------------|---------------------------------------------------|--------------------------------------|
/// | [`union`] | [`a` &#124; `b`] | `[a, b, c].`[`union`][multiway_union]`() ` | [`union_dyn!`]`(a, b, c)` |
/// | [`union`] | [`a` &#124; `b`] | `[a, b, c].`[`union`][multiway_union]`() ` | [`union_dyn!`]`(a, b, c)` |
/// | [`intersection`] | [`a & b`] | `[a, b, c].`[`intersection`][multiway_intersection]`() ` | [`intersection_dyn!`]`(a, b, c)`|
/// | [`difference`] | [`a - b`] | *n/a* | *n/a* |
/// | [`symmetric_difference`] | [`a ^ b`] | `[a, b, c].`[`symmetric_difference`][multiway_symmetric_difference]`() ` | [`symmetric_difference_dyn!`]`(a, b, c)` |
Expand Down Expand Up @@ -686,6 +687,67 @@ pub trait AnythingGoes<T: Integer>: Iterator<Item = RangeInclusive<T>> + FusedIt
impl<T: Integer, I> AnythingGoes<T> for I where I: Iterator<Item = RangeInclusive<T>> + FusedIterator
{}

/// `RangeOnce` is an iterator which emits a single `RangeInclusive` value before
/// fusing.
///
/// `RangeOnce` is analogous to [`core::iter::Once`], but modified to treat an
/// empty [`RangeInclusive`] as an empty [`Iterator`]. This allows `RangeOnce`
/// to be safely used as a [`SortedDisjoint`] Iterator.
///
/// # Example
///
/// ```
/// use range_set_blaze::{ RangeSetBlaze, RangeOnce };
///
/// let a = RangeOnce::new(0..=10);
/// let b = RangeOnce::new(3..=2); // empty range
/// let c = RangeOnce::new(5..=15);
///
/// let combined = RangeSetBlaze::from_sorted_disjoint(a | b | c);
/// assert_eq!(combined.into_string(), "0..=15");
/// ```
pub struct RangeOnce<T>(core::option::IntoIter<RangeInclusive<T>>);

impl<T: Integer> RangeOnce<T> {
/// Creates a new [`RangeOnce`] from a single range. See [`RangeOnce`] for details and examples.
pub fn new(range: RangeInclusive<T>) -> Self {
Self((!range.is_empty()).then_some(range).into_iter())
}
}

impl<T: Integer> From<RangeInclusive<T>> for RangeOnce<T> {
#[inline]
fn from(value: RangeInclusive<T>) -> Self {
Self::new(value)
}
}

impl<T: Integer> Iterator for RangeOnce<T> {
type Item = RangeInclusive<T>;

fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}

impl<T: Integer> DoubleEndedIterator for RangeOnce<T> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
}

impl<T: Integer> ExactSizeIterator for RangeOnce<T> {
fn len(&self) -> usize {
self.0.len()
}
}

impl<T: Integer> FusedIterator for RangeOnce<T> {}

macro_rules! impl_sorted_traits_and_ops {
($IterType:ty, $($more_generics:tt)*) => {
#[allow(single_use_lifetimes)]
Expand Down Expand Up @@ -767,5 +829,4 @@ impl_sorted_traits_and_ops!(RangesIter<'a, T>, 'a);
impl_sorted_traits_and_ops!(RangeValuesToRangesIter<T, VR, I>, VR: ValueRef, I: SortedDisjointMap<T, VR>);
impl_sorted_traits_and_ops!(SymDiffIter<T, I>, I: SortedStarts<T>);
impl_sorted_traits_and_ops!(UnionIter<T, I>, I: SortedStarts<T>);

// We're not allowed to define methods on outside types, so we only define the traits
impl_sorted_traits_and_ops!(RangeOnce<T>, 'ignore);
69 changes: 68 additions & 1 deletion tests/set_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#![cfg(test)]
use range_set_blaze::{
AssumeSortedStarts, IntoIter, IntoRangesIter, Iter, KMerge, MapIntoRangesIter, MapRangesIter,
Merge, RangeValuesIter, RangeValuesToRangesIter, RangesIter,
Merge, RangeOnce, RangeValuesIter, RangeValuesToRangesIter, RangesIter,
};

use wasm_bindgen_test::*;
Expand Down Expand Up @@ -615,6 +615,7 @@ fn tricky_case3() {
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[allow(unused_assignments)]
#[allow(clippy::reversed_empty_ranges)]
fn constructors() {
// #9: new
let mut range_set_blaze;
Expand All @@ -631,6 +632,9 @@ fn constructors() {
// #16 into / from iter (T,T) + SortedDisjoint
range_set_blaze = range_set_blaze.ranges().into_range_set_blaze();
range_set_blaze = RangeSetBlaze::from_sorted_disjoint(range_set_blaze.ranges());
// from RangeInclusive<T>
range_set_blaze = RangeSetBlaze::from(10..=20);
range_set_blaze = RangeSetBlaze::from(6..=5);
}

#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -2024,6 +2028,69 @@ fn test_every_sorted_disjoint_method() {
)*}}
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_empty_range() {
let a = RangeSetBlaze::from(0..=10);
let e = RangeSetBlaze::from(6..=5); // empty range

assert!(e.is_empty());
assert!((&a & &e).is_empty());
assert_eq!(&a | &e, a);
assert_eq!(&a - &e, a);
assert_eq!(&a ^ &e, a);
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[should_panic(expected = "start must be less or equal to end")]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_sorted_disjoint_empty_array() {
RangeSetBlaze::from_sorted_disjoint(CheckSortedDisjoint::new([6..=5]));
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_range_once_from() {
let mut r: RangeOnce<_> = (0..=15).into();
assert_eq!(r.next(), Some(0..=15));
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[allow(clippy::reversed_empty_ranges)]
fn test_range_once_empty() {
assert_eq!(RangeOnce::new(6..=5).next(), None);
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[allow(clippy::items_after_statements)]
fn test_range_once_sorted_disjoint() {
macro_rules! testiter {
() => {
RangeOnce::new(10..=20)
};
}

assert!(!testiter!().is_universal());
assert!(!testiter!().is_empty());
assert!((!testiter!()).equal(CheckSortedDisjoint::new([
-2_147_483_648..=9,
21..=2_147_483_647
])));
assert!((testiter!() | RangeOnce::new(15..=25)).equal(RangeOnce::new(10..=25)));
assert!((testiter!() & RangeOnce::new(15..=25)).equal(RangeOnce::new(15..=20)));
assert!(
(testiter!() ^ RangeOnce::new(15..=25)).equal(CheckSortedDisjoint::new([10..=14, 21..=25]))
);
assert!((testiter!() - RangeOnce::new(15..=25)).equal(CheckSortedDisjoint::new([10..=14])));

fn is_fused<T: FusedIterator>(_iter: T) {}
is_fused::<_>(testiter!());
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn multiway3() {
Expand Down