Skip to content

Commit 253bde3

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 253bde3

File tree

1 file changed

+83
-0
lines changed

1 file changed

+83
-0
lines changed

version-ranges/src/lib.rs

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

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

831909
impl<V: Display + Eq> Display for Ranges<V> {
@@ -1130,6 +1208,11 @@ pub mod tests {
11301208
}
11311209
assert!(simp.segments.len() <= range.segments.len())
11321210
}
1211+
1212+
#[test]
1213+
fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
1214+
Ranges::from_iter(segments.clone()).check_invariants();
1215+
}
11331216
}
11341217

11351218
#[test]

0 commit comments

Comments
 (0)