Skip to content

Commit fe7e010

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 2a37e13 commit fe7e010

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
@@ -879,6 +879,87 @@ impl<V> IntoIterator for Ranges<V> {
879879
}
880880
}
881881

882+
impl<V: Ord + Debug> FromIterator<(Bound<V>, Bound<V>)> for Ranges<V> {
883+
/// Constructor from arbitrary, unsorted and potentially overlapping ranges.
884+
///
885+
/// This is equivalent, but faster, to computing the [`Ranges::union`] of the
886+
/// [`Ranges::from_range_bounds`] of each segment.
887+
fn from_iter<T: IntoIterator<Item = (Bound<V>, Bound<V>)>>(iter: T) -> Self {
888+
// We have three constraints we need to fulfil:
889+
// 1. The segments are sorted, from lowest to highest (through `Ord`): By sorting.
890+
// 2. Each segment contains at least one version (start < end): By `union`.
891+
// 3. There is at least one version between two segments: By `union`.
892+
let mut segments: SmallVec<[Interval<V>; 1]> = SmallVec::new();
893+
894+
for segment in iter {
895+
if !valid_segment(&segment.start_bound(), &segment.end_bound()) {
896+
continue;
897+
}
898+
// Find where to insert the new segment
899+
let insertion_point = segments.partition_point(|elem: &Interval<V>| {
900+
cmp_bounds_start(elem.start_bound(), segment.start_bound())
901+
.unwrap()
902+
.is_lt()
903+
});
904+
// Is it overlapping with the previous segment?
905+
let previous_overlapping = insertion_point > 0
906+
&& !end_before_start_with_gap(
907+
&segments[insertion_point - 1].end_bound(),
908+
&segment.start_bound(),
909+
);
910+
911+
// Is it overlapping with the following segment?
912+
let next_overlapping = insertion_point < segments.len()
913+
&& !end_before_start_with_gap(
914+
&segment.end_bound(),
915+
&segments[insertion_point].start_bound(),
916+
);
917+
918+
match (previous_overlapping, next_overlapping) {
919+
(true, true) => {
920+
// previous: |-------|
921+
// segment: |-------|
922+
// following: |-----|
923+
//
924+
// final: |--------------------|
925+
// We merge all three segments into one, which is effectively removing one of
926+
// two previously inserted and changing the bounds on the other.
927+
let following = segments.remove(insertion_point);
928+
segments[insertion_point - 1].1 = following.1;
929+
}
930+
(true, false) => {
931+
// previous: |-----|
932+
// segment: |-----|
933+
// following: |-----|
934+
//
935+
// final: |---------| |-----|
936+
// We can reuse the existing element by extending it.
937+
segments[insertion_point - 1].1 = segment.1;
938+
}
939+
(false, true) => {
940+
// previous: |-----|
941+
// segment: |-----|
942+
// following: |-----|
943+
//
944+
// final: |-----| |---------|
945+
// We can reuse the existing element by extending it.
946+
segments[insertion_point].0 = segment.0;
947+
}
948+
(false, false) => {
949+
// previous: |-----|
950+
// segment: |-----|
951+
// following: |-----|
952+
//
953+
// final: |-----| |-----| |-----|
954+
segments.insert(insertion_point, segment);
955+
}
956+
}
957+
}
958+
959+
Self { segments }.check_invariants()
960+
}
961+
}
962+
882963
// REPORT ######################################################################
883964

884965
impl<V: Display + Eq> Display for Ranges<V> {
@@ -1183,6 +1264,12 @@ pub mod tests {
11831264
}
11841265
assert!(simp.segments.len() <= range.segments.len())
11851266
}
1267+
1268+
#[test]
1269+
fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound<u32>, Bound<u32>)>(), ..30)) {
1270+
// We check invariants in the method.
1271+
Ranges::from_iter(segments.clone());
1272+
}
11861273
}
11871274

11881275
#[test]
@@ -1219,6 +1306,15 @@ pub mod tests {
12191306
);
12201307
}
12211308

1309+
#[test]
1310+
fn from_iter_regression() {
1311+
Ranges::from_iter([
1312+
(Included(0), Included(0)),
1313+
(Excluded(1u32), Unbounded),
1314+
(Included(0), Included(0)),
1315+
]);
1316+
}
1317+
12221318
#[test]
12231319
fn version_ord() {
12241320
let versions: &[Ranges<u32>] = &[

0 commit comments

Comments
 (0)