From e7b2db6a0c0d0202694754efb77625ee43a14f5a Mon Sep 17 00:00:00 2001 From: "sinu.eth" <65924192+sinui0@users.noreply.github.com> Date: Wed, 4 Sep 2024 19:29:29 -0700 Subject: [PATCH] feat(utils): additional set operations (#39) * rename traits * subset * intersection * prove set intersection * assert invariants * fix doctest --- Cargo.toml | 2 +- spansy/src/http/types.rs | 2 +- spansy/src/json/types.rs | 2 +- utils/fuzz/Cargo.toml | 28 ++- .../fuzz_targets/range_intersection_set.rs | 25 +++ utils/fuzz/fuzz_targets/range_subset_set.rs | 20 ++ .../fuzz/fuzz_targets/set_intersection_set.rs | 24 +++ utils/fuzz/fuzz_targets/set_subset_set.rs | 19 ++ utils/src/range/difference.rs | 20 +- utils/src/range/intersection.rs | 197 ++++++++++++++++++ utils/src/range/mod.rs | 108 +++------- utils/src/range/subset.rs | 166 +++++++++++++++ utils/src/range/union.rs | 16 +- 13 files changed, 524 insertions(+), 105 deletions(-) create mode 100644 utils/fuzz/fuzz_targets/range_intersection_set.rs create mode 100644 utils/fuzz/fuzz_targets/range_subset_set.rs create mode 100644 utils/fuzz/fuzz_targets/set_intersection_set.rs create mode 100644 utils/fuzz/fuzz_targets/set_subset_set.rs create mode 100644 utils/src/range/intersection.rs create mode 100644 utils/src/range/subset.rs diff --git a/Cargo.toml b/Cargo.toml index 40f1827..6b9562d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["utils", "utils-aio", "spansy", "serio", "uid-mux"] +members = ["utils", "utils-aio", "spansy", "serio", "uid-mux", "utils/fuzz"] [workspace.dependencies] tlsn-utils = { path = "utils" } diff --git a/spansy/src/http/types.rs b/spansy/src/http/types.rs index aa4bd27..c601bee 100644 --- a/spansy/src/http/types.rs +++ b/spansy/src/http/types.rs @@ -1,4 +1,4 @@ -use utils::range::{RangeDifference, RangeSet, ToRangeSet}; +use utils::range::{Difference, RangeSet, ToRangeSet}; use crate::{json::JsonValue, Span, Spanned}; diff --git a/spansy/src/json/types.rs b/spansy/src/json/types.rs index 489e73d..f904d04 100644 --- a/spansy/src/json/types.rs +++ b/spansy/src/json/types.rs @@ -1,6 +1,6 @@ use std::ops::{Index, Range}; -use utils::range::{RangeDifference, RangeSet, ToRangeSet}; +use utils::range::{Difference, RangeSet, ToRangeSet}; use crate::{Span, Spanned}; diff --git a/utils/fuzz/Cargo.toml b/utils/fuzz/Cargo.toml index e58b4ef..9d1ee18 100644 --- a/utils/fuzz/Cargo.toml +++ b/utils/fuzz/Cargo.toml @@ -13,10 +13,6 @@ libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } [dependencies.tlsn-utils] path = ".." -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - [profile.release] debug = 1 @@ -44,6 +40,18 @@ path = "fuzz_targets/range_diff_set.rs" test = false doc = false +[[bin]] +name = "range_intersection_set" +path = "fuzz_targets/range_intersection_set.rs" +test = false +doc = false + +[[bin]] +name = "range_subset_set" +path = "fuzz_targets/range_subset_set.rs" +test = false +doc = false + [[bin]] name = "set_union_range" path = "fuzz_targets/set_union_range.rs" @@ -62,6 +70,18 @@ path = "fuzz_targets/set_diff_set.rs" test = false doc = false +[[bin]] +name = "set_intersection_set" +path = "fuzz_targets/set_intersection_set.rs" +test = false +doc = false + +[[bin]] +name = "set_subset_set" +path = "fuzz_targets/set_subset_set.rs" +test = false +doc = false + [[bin]] name = "set_diff_range" path = "fuzz_targets/set_diff_range.rs" diff --git a/utils/fuzz/fuzz_targets/range_intersection_set.rs b/utils/fuzz/fuzz_targets/range_intersection_set.rs new file mode 100644 index 0000000..9f94cc7 --- /dev/null +++ b/utils/fuzz/fuzz_targets/range_intersection_set.rs @@ -0,0 +1,25 @@ +#![no_main] + +use std::collections::HashSet; +use std::ops::Range; + +use libfuzzer_sys::fuzz_target; + +use tlsn_utils_fuzz::{assert_invariants, SmallSet}; + +use utils::range::*; + +fuzz_target!(|r: (Range, SmallSet)| { + let s1 = r.0; + let s2: RangeSet = r.1.into(); + + let h1: HashSet = HashSet::from_iter(s1.clone()); + let h2: HashSet = HashSet::from_iter(s2.iter()); + + let intersection = s1.intersection(&s2); + let h3: HashSet = HashSet::from_iter(intersection.iter()); + + assert_eq!(h3, h1.intersection(&h2).copied().collect::>()); + + assert_invariants(intersection); +}); diff --git a/utils/fuzz/fuzz_targets/range_subset_set.rs b/utils/fuzz/fuzz_targets/range_subset_set.rs new file mode 100644 index 0000000..1dd5be1 --- /dev/null +++ b/utils/fuzz/fuzz_targets/range_subset_set.rs @@ -0,0 +1,20 @@ +#![no_main] + +use std::collections::HashSet; +use std::ops::Range; + +use libfuzzer_sys::fuzz_target; + +use tlsn_utils_fuzz::SmallSet; + +use utils::range::*; + +fuzz_target!(|r: (Range, SmallSet)| { + let s1 = r.0; + let s2: RangeSet = r.1.into(); + + let h1: HashSet = HashSet::from_iter(s1.clone()); + let h2: HashSet = HashSet::from_iter(s2.iter()); + + assert_eq!(s1.is_subset(&s2), h1.is_subset(&h2)); +}); diff --git a/utils/fuzz/fuzz_targets/set_intersection_set.rs b/utils/fuzz/fuzz_targets/set_intersection_set.rs new file mode 100644 index 0000000..c838ae3 --- /dev/null +++ b/utils/fuzz/fuzz_targets/set_intersection_set.rs @@ -0,0 +1,24 @@ +#![no_main] + +use std::collections::HashSet; + +use libfuzzer_sys::fuzz_target; + +use tlsn_utils_fuzz::{assert_invariants, SmallSet}; + +use utils::range::*; + +fuzz_target!(|r: (SmallSet, SmallSet)| { + let s1: RangeSet = r.0.into(); + let s2: RangeSet = r.1.into(); + + let h1: HashSet = HashSet::from_iter(s1.iter()); + let h2: HashSet = HashSet::from_iter(s2.iter()); + + let intersection = s1.intersection(&s2); + let h3: HashSet = HashSet::from_iter(intersection.iter()); + + assert_eq!(h3, h1.intersection(&h2).copied().collect::>()); + + assert_invariants(intersection); +}); diff --git a/utils/fuzz/fuzz_targets/set_subset_set.rs b/utils/fuzz/fuzz_targets/set_subset_set.rs new file mode 100644 index 0000000..cf9d1c1 --- /dev/null +++ b/utils/fuzz/fuzz_targets/set_subset_set.rs @@ -0,0 +1,19 @@ +#![no_main] + +use std::collections::HashSet; + +use libfuzzer_sys::fuzz_target; + +use tlsn_utils_fuzz::SmallSet; + +use utils::range::*; + +fuzz_target!(|r: (SmallSet, SmallSet)| { + let s1: RangeSet = r.0.into(); + let s2: RangeSet = r.1.into(); + + let h1: HashSet = HashSet::from_iter(s1.iter()); + let h2: HashSet = HashSet::from_iter(s2.iter()); + + assert_eq!(s1.is_subset(&s2), h1.is_subset(&h2)); +}); diff --git a/utils/src/range/difference.rs b/utils/src/range/difference.rs index 47bbfd1..be18410 100644 --- a/utils/src/range/difference.rs +++ b/utils/src/range/difference.rs @@ -1,10 +1,8 @@ use std::ops::Range; -use crate::range::{ - RangeDifference, RangeDisjoint, RangeSet, RangeSubset, RangeSuperset, RangeUnion, -}; +use crate::range::{Difference, Disjoint, RangeSet, Subset, Union}; -impl RangeDifference> for Range { +impl Difference> for Range { type Output = RangeSet; fn difference(&self, other: &Range) -> Self::Output { @@ -14,8 +12,8 @@ impl RangeDifference> for Range { return RangeSet::from(self.clone()); } - // If other is a superset of self, return an empty set. - if other.is_superset(self) { + // If other contains self, return an empty set. + if self.is_subset(other) { return RangeSet::default(); } @@ -38,9 +36,9 @@ impl RangeDifference> for Range { } } -impl RangeDifference> for Range +impl Difference> for Range where - RangeSet: RangeDifference, Output = RangeSet>, + RangeSet: Difference, Output = RangeSet>, { type Output = RangeSet; @@ -59,7 +57,7 @@ where } } -impl RangeDifference> for RangeSet { +impl Difference> for RangeSet { type Output = RangeSet; fn difference(&self, other: &Range) -> Self::Output { @@ -80,7 +78,7 @@ impl RangeDifference> for RangeSet { break; } // If the current range is entirely contained within other - else if other.is_superset(&ranges[i]) { + else if ranges[i].is_subset(other) { ranges.remove(i); continue; } @@ -113,7 +111,7 @@ impl RangeDifference> for RangeSet { } } -impl RangeDifference> for RangeSet { +impl Difference> for RangeSet { type Output = RangeSet; fn difference(&self, other: &RangeSet) -> Self::Output { diff --git a/utils/src/range/intersection.rs b/utils/src/range/intersection.rs new file mode 100644 index 0000000..e41c647 --- /dev/null +++ b/utils/src/range/intersection.rs @@ -0,0 +1,197 @@ +use crate::range::{Intersection, Range, RangeSet}; + +impl Intersection> for Range { + type Output = Option>; + + fn intersection(&self, other: &Range) -> Self::Output { + let start = self.start.max(other.start); + let end = self.end.min(other.end); + + if start < end { + Some(Range { start, end }) + } else { + None + } + } +} + +impl Intersection> for Range { + type Output = RangeSet; + + fn intersection(&self, other: &RangeSet) -> Self::Output { + let mut set = RangeSet::default(); + + for other in &other.ranges { + if self.end <= other.start { + // `self` is leftward of `other`, so we can break early. + break; + } else if let Some(intersection) = self.intersection(other) { + // Given that `other` contains sorted, non-adjacent, non-intersecting, and non-empty + // ranges, the new set will also have these properties. + set.ranges.push(intersection); + } + } + + set + } +} + +impl Intersection> for RangeSet { + type Output = RangeSet; + + fn intersection(&self, other: &Range) -> Self::Output { + other.intersection(self) + } +} + +impl Intersection> for RangeSet { + type Output = RangeSet; + + fn intersection(&self, other: &RangeSet) -> Self::Output { + let mut set = RangeSet::default(); + + let mut i = 0; + let mut j = 0; + + while i < self.ranges.len() && j < other.ranges.len() { + let a = &self.ranges[i]; + let b = &other.ranges[j]; + + if a.end <= b.start { + // `a` is leftward of `b`, so we can proceed to the next range in `self`. + i += 1; + } else if b.end <= a.start { + // `b` is leftward of `a`, so we can proceed to the next range in `other`. + j += 1; + } else if let Some(intersection) = a.intersection(b) { + // Given that `self` and `other` contain sorted, non-adjacent, non-intersecting, and + // non-empty ranges, the new set will also have these properties. + set.ranges.push(intersection); + + if a.end <= b.end { + i += 1; + } + + if b.end <= a.end { + j += 1; + } + } + } + + set + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use itertools::iproduct; + + use crate::range::assert_invariants; + + use super::*; + + #[test] + fn test_range_intersection_range() { + assert!((0..0).intersection(&(0..0)).is_none()); + assert!((0..1).intersection(&(0..0)).is_none()); + assert!((0..0).intersection(&(0..1)).is_none()); + assert_eq!((0..1).intersection(&(0..1)), Some(0..1)); + assert_eq!((0..2).intersection(&(0..1)), Some(0..1)); + assert_eq!((0..1).intersection(&(0..2)), Some(0..1)); + assert_eq!((0..2).intersection(&(0..2)), Some(0..2)); + assert_eq!((0..2).intersection(&(1..2)), Some(1..2)); + assert_eq!((1..2).intersection(&(0..2)), Some(1..2)); + } + + #[test] + fn test_range_intersection_set() { + let set = RangeSet::from(vec![0..1, 2..3, 4..5]); + + assert_eq!(set.intersection(&(0..0)), RangeSet::default()); + assert_eq!(set.intersection(&(0..1)), RangeSet::from(vec![0..1])); + assert_eq!(set.intersection(&(0..2)), RangeSet::from(vec![0..1])); + assert_eq!(set.intersection(&(0..3)), RangeSet::from(vec![0..1, 2..3])); + assert_eq!(set.intersection(&(0..4)), RangeSet::from(vec![0..1, 2..3])); + assert_eq!(set.intersection(&(1..3)), RangeSet::from(vec![2..3])); + assert_eq!( + set.intersection(&(0..6)), + RangeSet::from(vec![0..1, 2..3, 4..5]) + ); + assert_eq!( + set.intersection(&(0..6)), + RangeSet::from(vec![0..1, 2..3, 4..5]) + ); + } + + #[test] + fn test_set_intersection_set() { + let set = RangeSet::from(vec![0..1, 2..3, 5..6]); + + assert_eq!(set.intersection(&RangeSet::default()), RangeSet::default()); + assert_eq!( + set.intersection(&RangeSet::from(vec![1..2])), + RangeSet::default() + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![3..5])), + RangeSet::default() + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![7..8])), + RangeSet::default() + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![0..1])), + RangeSet::from(vec![0..1]) + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![0..2])), + RangeSet::from(vec![0..1]) + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![0..3])), + RangeSet::from(vec![0..1, 2..3]) + ); + assert_eq!(set.intersection(&RangeSet::from(vec![0..6])), set); + assert_eq!( + set.intersection(&RangeSet::from(vec![1..6])), + RangeSet::from(vec![2..3, 5..6]) + ); + assert_eq!( + set.intersection(&RangeSet::from(vec![2..3, 5..6])), + RangeSet::from(vec![2..3, 5..6]) + ); + } + + #[test] + #[ignore = "expensive"] + fn test_prove_set_intersection_set_8x2_8x2() { + for (xs, xe, ys, ye, ws, we, zs, ze) in + iproduct!(0..8, 0..8, 0..8, 0..8, 0..8, 0..8, 0..8, 0..8) + { + let s1 = RangeSet::new(&[(xs..xe), (ys..ye)]); + let s2 = RangeSet::new(&[(ws..we), (zs..ze)]); + + let h1 = s1.iter().collect::>(); + let h2 = s2.iter().collect::>(); + + let actual = s1.intersection(&s2); + let h3 = HashSet::::from_iter(actual.iter()); + + assert_invariants(&actual); + + assert_eq!( + h3, + h1.intersection(&h2).copied().collect::>(), + "{:?} {:?} {:?} {:?} => {:?}", + xs..xe, + ys..ye, + ws..we, + zs..ze, + h3 + ); + } + } +} diff --git a/utils/src/range/mod.rs b/utils/src/range/mod.rs index e4b1de0..e4785d9 100644 --- a/utils/src/range/mod.rs +++ b/utils/src/range/mod.rs @@ -1,5 +1,7 @@ mod difference; mod index; +mod intersection; +mod subset; mod union; pub use index::IndexRanges; @@ -38,7 +40,6 @@ use std::ops::{Add, Range, Sub}; /// assert_eq!(a.union(&(0..0)), RangeSet::from([10..20])); /// /// // Comparison -/// assert!(a.is_superset(&(15..18))); /// assert!(a.is_subset(&(0..30))); /// assert!(a.is_disjoint(&(0..10))); /// assert_eq!(a.clone(), RangeSet::from(a)); @@ -98,7 +99,7 @@ impl RangeSet { /// The `RangeSet` is constructed by computing the union of the given ranges. pub fn new(ranges: &[Range]) -> Self where - Self: RangeUnion, Output = Self>, + Self: Union, Output = Self>, { let mut set = Self::default(); @@ -384,25 +385,25 @@ impl ToRangeSet for Range { } } -pub trait RangeDisjoint { +pub trait Disjoint { /// Returns `true` if the range is disjoint with `other`. #[must_use] fn is_disjoint(&self, other: &Rhs) -> bool; } -pub trait RangeSuperset { - /// Returns `true` if `self` is a superset of `other`. +pub trait Contains { + /// Returns `true` if `self` contains `other`. #[must_use] - fn is_superset(&self, other: &Rhs) -> bool; + fn contains(&self, other: &Rhs) -> bool; } -pub trait RangeSubset { +pub trait Subset { /// Returns `true` if `self` is a subset of `other`. #[must_use] fn is_subset(&self, other: &Rhs) -> bool; } -pub trait RangeDifference { +pub trait Difference { type Output; /// Returns the set difference of `self` and `other`. @@ -410,7 +411,7 @@ pub trait RangeDifference { fn difference(&self, other: &Rhs) -> Self::Output; } -pub trait RangeUnion { +pub trait Union { type Output; /// Returns the set union of `self` and `other`. @@ -418,6 +419,14 @@ pub trait RangeUnion { fn union(&self, other: &Rhs) -> Self::Output; } +pub trait Intersection { + type Output; + + /// Returns the set intersection of `self` and `other`. + #[must_use] + fn intersection(&self, other: &Rhs) -> Self::Output; +} + /// A type which successor and predecessor operations can be performed on. /// /// Similar to `std::iter::Step`, but not nightly-only. @@ -447,52 +456,37 @@ macro_rules! impl_step { impl_step!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); -impl RangeDisjoint> for Range { +impl Disjoint> for Range { fn is_disjoint(&self, other: &Range) -> bool { self.start >= other.end || self.end <= other.start } } -impl RangeDisjoint> for Range { +impl Disjoint> for Range { fn is_disjoint(&self, other: &RangeSet) -> bool { other.ranges.iter().all(|range| self.is_disjoint(range)) } } -impl RangeDisjoint> for RangeSet { +impl Disjoint> for RangeSet { fn is_disjoint(&self, other: &RangeSet) -> bool { self.ranges.iter().all(|range| range.is_disjoint(other)) } } -impl RangeDisjoint> for RangeSet { +impl Disjoint> for RangeSet { fn is_disjoint(&self, other: &Range) -> bool { other.is_disjoint(self) } } -impl RangeSuperset> for Range { - fn is_superset(&self, other: &Range) -> bool { - self.start <= other.start && self.end >= other.end - } -} - -impl RangeSuperset> for Range { - fn is_superset(&self, other: &RangeSet) -> bool { - other.ranges.iter().all(|range| self.is_superset(range)) - } -} - -impl RangeSubset> for Range { - fn is_subset(&self, other: &Range) -> bool { - self.start >= other.start && self.end <= other.end - } -} - -impl RangeSubset> for Range { - fn is_subset(&self, other: &RangeSet) -> bool { - other.ranges.iter().any(|range| self.is_subset(range)) - } +/// Asserts that the ranges of the given set are sorted, non-adjacent, non-intersecting, and non-empty. +#[cfg(test)] +pub fn assert_invariants(set: &RangeSet) { + assert!(set.ranges.windows(2).all(|w| w[0].start < w[1].start + && w[0].end < w[1].start + && w[0].start < w[0].end + && w[1].start < w[1].end)); } #[cfg(test)] @@ -523,50 +517,6 @@ mod tests { assert!(!a.is_disjoint(&(10..20))); } - #[test] - fn test_range_superset() { - let a = 10..20; - - // rightward - assert!(!a.is_superset(&(20..30))); - // rightward aligned - assert!(!a.is_superset(&(19..25))); - // leftward - assert!(!a.is_superset(&(0..10))); - // leftward aligned - assert!(!a.is_superset(&(5..11))); - // rightward subset - assert!(a.is_superset(&(15..20))); - // leftward subset - assert!(a.is_superset(&(10..15))); - // superset - assert!(!a.is_superset(&(5..25))); - // equal - assert!(a.is_superset(&(10..20))); - } - - #[test] - fn test_range_subset() { - let a = 10..20; - - // rightward - assert!(!a.is_subset(&(20..30))); - // rightward aligned - assert!(!a.is_subset(&(19..25))); - // leftward - assert!(!a.is_subset(&(0..10))); - // leftward aligned - assert!(!a.is_subset(&(5..11))); - // rightward subset - assert!(!a.is_subset(&(15..20))); - // leftward subset - assert!(!a.is_subset(&(10..15))); - // superset - assert!(a.is_subset(&(5..25))); - // equal - assert!(a.is_subset(&(10..20))); - } - #[test] fn test_range_set_iter() { let a = RangeSet::from([(10..20), (30..40), (50..60)]); diff --git a/utils/src/range/subset.rs b/utils/src/range/subset.rs new file mode 100644 index 0000000..dc3dc49 --- /dev/null +++ b/utils/src/range/subset.rs @@ -0,0 +1,166 @@ +use crate::range::{Range, RangeSet, Subset}; + +impl Subset> for Range { + fn is_subset(&self, other: &Range) -> bool { + self.start >= other.start && self.end <= other.end + } +} + +impl Subset> for Range { + fn is_subset(&self, other: &RangeSet) -> bool { + if self.is_empty() { + // empty range is subset of any set + return true; + } else if other.ranges.is_empty() { + // non-empty range is not subset of empty set + return false; + } + + for other in &other.ranges { + if self.start >= other.end { + // self is rightward of other, proceed to next other + continue; + } else { + return self.is_subset(other); + } + } + + false + } +} + +impl Subset> for RangeSet { + fn is_subset(&self, other: &Range) -> bool { + let (Some(start), Some(end)) = (self.min(), self.end()) else { + // empty set is subset of any set + return true; + }; + + // check if the outer bounds of this set are within the range + start >= other.start && end <= other.end + } +} + +impl Subset> for RangeSet { + fn is_subset(&self, other: &RangeSet) -> bool { + if self.ranges.is_empty() { + // empty set is subset of any set + return true; + } else if other.ranges.is_empty() { + // non-empty set is not subset of empty set + return false; + } + + let mut i = 0; + let mut j = 0; + + while i < self.ranges.len() && j < other.ranges.len() { + let a = &self.ranges[i]; + let b = &other.ranges[j]; + + if a.start >= b.end { + // a is rightward of b, proceed to next b + j += 1; + } else if a.is_subset(b) { + // a is subset of b, proceed to next a + i += 1; + } else { + // self contains values not in other + return false; + } + } + + // If we've reached the end of self, then all ranges are contained in other. + i == self.ranges.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_range_subset_of_range() { + let a = 10..20; + + // empty + assert!(!a.is_subset(&(0..0))); + // rightward + assert!(!a.is_subset(&(20..30))); + // rightward aligned + assert!(!a.is_subset(&(19..25))); + // leftward + assert!(!a.is_subset(&(0..10))); + // leftward aligned + assert!(!a.is_subset(&(5..11))); + // rightward subset + assert!(!a.is_subset(&(15..20))); + // leftward subset + assert!(!a.is_subset(&(10..15))); + // superset + assert!(a.is_subset(&(5..25))); + // equal + assert!(a.is_subset(&(10..20))); + } + + #[test] + fn test_range_subset_of_rangeset() { + let a = 10..20; + + let empty = RangeSet::::default(); + + // empty set is subset of any range + assert!(empty.is_subset(&a)); + // non-empty range is not subset of empty set + assert!(!a.is_subset(&empty)); + + assert!(a.is_subset(&RangeSet::from(vec![0..20]))); + assert!(a.is_subset(&RangeSet::from(vec![10..20]))); + assert!(!a.is_subset(&RangeSet::from(vec![0..10]))); + assert!(!a.is_subset(&RangeSet::from(vec![20..30]))); + assert!(!a.is_subset(&RangeSet::from(vec![0..10, 20..30]))); + } + + #[test] + fn test_rangeset_subset_of_range() { + let a = RangeSet::from(vec![10..20, 30..40]); + + assert!(!a.is_subset(&(0..0))); + assert!(!a.is_subset(&(0..10))); + assert!(!a.is_subset(&(0..15))); + assert!(!a.is_subset(&(0..20))); + assert!(!a.is_subset(&(20..30))); + assert!(!a.is_subset(&(20..40))); + assert!(!a.is_subset(&(20..50))); + assert!(!a.is_subset(&(30..40))); + assert!(!a.is_subset(&(11..40))); + assert!(!a.is_subset(&(10..39))); + + assert!(a.is_subset(&(10..40))); + } + + #[test] + fn test_rangeset_subset_of_rangeset() { + let empty = RangeSet::::default(); + + // empty set is subset of itself + assert!(empty.is_subset(&empty)); + // empty set is subset of non-empty set + assert!(empty.is_subset(&RangeSet::from(vec![10..20]))); + // non-empty set is not subset of empty set + assert!(!RangeSet::from(vec![10..20]).is_subset(&empty)); + + let a = RangeSet::from(vec![10..20, 30..40]); + + // equal + assert!(a.is_subset(&a)); + + assert!(a.is_subset(&RangeSet::from(vec![0..20, 30..50]))); + assert!(a.is_subset(&RangeSet::from(vec![10..20, 30..40, 40..50]))); + assert!(!a.is_subset(&RangeSet::from(vec![10..20]))); + assert!(!a.is_subset(&RangeSet::from(vec![30..40]))); + assert!(!a.is_subset(&RangeSet::from(vec![0..20, 30..39]))); + assert!(!a.is_subset(&RangeSet::from(vec![10..19, 30..40]))); + assert!(!a.is_subset(&RangeSet::from(vec![0..10, 30..40]))); + } +} diff --git a/utils/src/range/union.rs b/utils/src/range/union.rs index 6279df3..bf03719 100644 --- a/utils/src/range/union.rs +++ b/utils/src/range/union.rs @@ -1,18 +1,18 @@ use std::ops::Range; -use crate::range::{RangeDisjoint, RangeSet, RangeSuperset, RangeUnion}; +use crate::range::{Disjoint, RangeSet, Subset, Union}; -impl RangeUnion> for Range { +impl Union> for Range { type Output = RangeSet; fn union(&self, other: &Range) -> Self::Output { // If the two are equal, or other is a subset, return self. - if self == other || self.is_superset(other) { + if self == other || other.is_subset(self) { return RangeSet::from(self.clone()); } - // If other is a superset, return other. - if other.is_superset(self) { + // If other contains self, return other. + if self.is_subset(other) { return RangeSet::from(other.clone()); } @@ -34,7 +34,7 @@ impl RangeUnion> for Range { } } -impl RangeUnion> for Range { +impl Union> for Range { type Output = RangeSet; fn union(&self, other: &RangeSet) -> Self::Output { @@ -74,7 +74,7 @@ impl RangeUnion> for Range { } } -impl RangeUnion> for RangeSet { +impl Union> for RangeSet { type Output = RangeSet; fn union(&self, other: &Range) -> Self::Output { @@ -82,7 +82,7 @@ impl RangeUnion> for RangeSet { } } -impl RangeUnion> for RangeSet { +impl Union> for RangeSet { type Output = RangeSet; fn union(&self, other: &RangeSet) -> Self::Output {