Skip to content

Commit faedeec

Browse files
committed
Add FromIter for Ranges
Add a method to construct ranges from an iterator of arbitrary segments. This allows to `.collect()` an iterator of tuples of bounds. This is more ergonomic than folding the previous ranges with the next segment each time, and also faster. Split out from #273 Closes astral-sh#33 Fixes #249
1 parent 216f3fd commit faedeec

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

version-ranges/src/lib.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,87 @@ impl<V: Ord + Clone> Ranges<V> {
826826
}
827827
}
828828

829+
impl<V: Ord + Debug> FromIterator<(Bound<V>, Bound<V>)> for Ranges<V> {
830+
/// Constructor from arbitrary, unsorted and potentially overlapping ranges.
831+
///
832+
/// This is equivalent to computing the [`Ranges::union`] of the [`Range::from_range_bounds`] of
833+
/// each segment.
834+
fn from_iter<T: IntoIterator<Item = (Bound<V>, Bound<V>)>>(iter: T) -> Self {
835+
// We have three constraints we need to fulfil:
836+
// 1. The segments are sorted, from lowest to highest (through `Ord`): By sorting.
837+
// 2. Each segment contains at least one version (start < end): By `union`.
838+
// 3. There is at least one version between two segments: By `union`.
839+
let mut segments: SmallVec<[Interval<V>; 1]> = SmallVec::new();
840+
841+
for segment in iter {
842+
if !valid_segment(&segment.start_bound(), &segment.end_bound()) {
843+
continue;
844+
}
845+
// Find where to insert the new segment
846+
let insertion_point = segments.partition_point(|elem: &Interval<V>| {
847+
cmp_bounds_start(elem.start_bound(), segment.start_bound())
848+
.unwrap()
849+
.is_lt()
850+
});
851+
// Is it overlapping with the previous segment?
852+
let previous_overlapping = insertion_point > 0
853+
&& !end_before_start_with_gap(
854+
&segments[insertion_point - 1].end_bound(),
855+
&segment.start_bound(),
856+
);
857+
858+
// Is it overlapping with the following segment?
859+
let next_overlapping = insertion_point < segments.len()
860+
&& !end_before_start_with_gap(
861+
&segment.end_bound(),
862+
&segments[insertion_point].start_bound(),
863+
);
864+
865+
match (previous_overlapping, next_overlapping) {
866+
(true, true) => {
867+
// previous: |-------|
868+
// segment: |-------|
869+
// following: |-----|
870+
//
871+
// final: |--------------------|
872+
// We merge all three segments into one, which is effectively removing one of
873+
// two previously inserted and changing the bounds on the other.
874+
let following = segments.remove(insertion_point);
875+
segments[insertion_point - 1].1 = following.1;
876+
}
877+
(true, false) => {
878+
// previous: |-----|
879+
// segment: |-----|
880+
// following: |-----|
881+
//
882+
// final: |---------| |-----|
883+
// We can reuse the existing element by extending it.
884+
segments[insertion_point - 1].1 = segment.1;
885+
}
886+
(false, true) => {
887+
// previous: |-----|
888+
// segment: |-----|
889+
// following: |-----|
890+
//
891+
// final: |-----| |---------|
892+
// We can reuse the existing element by extending it.
893+
segments[insertion_point].0 = segment.0;
894+
}
895+
(false, false) => {
896+
// previous: |-----|
897+
// segment: |-----|
898+
// following: |-----|
899+
//
900+
// final: |-----| |-----| |-----|
901+
segments.insert(insertion_point, segment);
902+
}
903+
}
904+
}
905+
906+
Self { segments }
907+
}
908+
}
909+
829910
// REPORT ######################################################################
830911

831912
impl<V: Display + Eq> Display for Ranges<V> {
@@ -1130,6 +1211,12 @@ pub mod tests {
11301211
}
11311212
assert!(simp.segments.len() <= range.segments.len())
11321213
}
1214+
1215+
#[test]
1216+
fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
1217+
// We check invariants in the method.
1218+
Ranges::from_iter(segments.clone());
1219+
}
11331220
}
11341221

11351222
#[test]
@@ -1166,6 +1253,15 @@ pub mod tests {
11661253
);
11671254
}
11681255

1256+
#[test]
1257+
fn from_iter_regression() {
1258+
Ranges::from_iter([
1259+
(Included(0), Included(0)),
1260+
(Excluded(1u32), Unbounded),
1261+
(Included(0), Included(0)),
1262+
]);
1263+
}
1264+
11691265
#[test]
11701266
fn version_ord() {
11711267
let versions: &[Ranges<u32>] = &[

0 commit comments

Comments
 (0)