Skip to content

Commit 51416ce

Browse files
committed
PartialOrd utility functions, README fix
1 parent 93f3994 commit 51416ce

File tree

3 files changed

+26
-36
lines changed

3 files changed

+26
-36
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ Rather than try to persuade you with words, this repository aims to show by exam
4747
- [Elementary graph algorithms](src/graph/util.rs): minimum spanning tree, Euler path, Dijkstra's algorithm, DFS iteration
4848
- [Network flows](src/graph/flow.rs): Dinic's blocking flow, Hopcroft-Karp bipartite matching, min cost max flow
4949
- [Connected components](src/graph/connectivity.rs): 2-edge-, 2-vertex- and strongly connected components, bridges, articulation points, topological sort, 2-SAT
50-
- [Associative range query](src/range_query): known colloquially as *segtrees*, coordinate compression, and Mo's query square root decomposition
51-
- [Number thery](src/math/mod.rs): canonical solution to Bezout's identity, Miller's primality test
50+
- [Associative range query](src/range_query): known colloquially as *segtrees*, coordinate compression, convex hull trick, and Mo's query square root decomposition
51+
- [Number theory](src/math/mod.rs): canonical solution to Bezout's identity, Miller's primality test
5252
- [Arithmetic](src/math/num.rs): rational and complex numbers, linear algebra, safe modular arithmetic
5353
- [FFT](src/math/fft.rs): fast Fourier transform, number theoretic transform, convolution
5454
- [Scanner](src/scanner.rs): utility for reading input data ergonomically

src/range_query/mod.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,24 @@ pub use dynamic_arq::{ArqView, DynamicArq};
66
pub use specs::ArqSpec;
77
pub use static_arq::StaticArq;
88

9-
/// Assuming slice is sorted, returns the minimum i for which slice[i] >= key,
10-
/// or slice.len() if no such i exists
11-
pub fn slice_lower_bound<T: Ord>(slice: &[T], key: &T) -> usize {
9+
/// A comparator on partially ordered elements, that panics if they are incomparable
10+
pub fn asserting_cmp<T: PartialOrd>(a: &T, b: &T) -> std::cmp::Ordering {
11+
a.partial_cmp(b).expect("Comparing incomparable elements")
12+
}
13+
14+
/// Assuming slice is totally ordered and sorted, returns the minimum i for which
15+
/// slice[i] >= key, or slice.len() if no such i exists
16+
pub fn slice_lower_bound<T: PartialOrd>(slice: &[T], key: &T) -> usize {
1217
slice
13-
.binary_search_by(|x| x.cmp(key).then(std::cmp::Ordering::Greater))
18+
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Greater))
1419
.unwrap_err()
1520
}
1621

17-
/// Assuming slice is sorted, returns the minimum i for which slice[i] > key,
18-
/// or slice.len() if no such i exists
19-
pub fn slice_upper_bound<T: Ord>(slice: &[T], key: &T) -> usize {
22+
/// Assuming slice is totally ordered and sorted, returns the minimum i for which
23+
/// slice[i] > key, or slice.len() if no such i exists
24+
pub fn slice_upper_bound<T: PartialOrd>(slice: &[T], key: &T) -> usize {
2025
slice
21-
.binary_search_by(|x| x.cmp(key).then(std::cmp::Ordering::Less))
26+
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Less))
2227
.unwrap_err()
2328
}
2429

src/range_query/sqrt_decomp.rs

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub struct PiecewiseLinearFn {
114114
impl PiecewiseLinearFn {
115115
/// For N inserts interleaved with Q queries, a threshold of N/sqrt(Q) yields
116116
/// O(N sqrt Q + Q log N) time complexity. If all queries come after all inserts,
117-
/// a threshold of 0 yields O(N + Q log N) time complexity.
117+
/// any threshold less than N (e.g., 0) yields O(N + Q log N) time complexity.
118118
pub fn with_merge_threshold(merge_threshold: usize) -> Self {
119119
Self {
120120
sorted_lines: vec![],
@@ -131,15 +131,14 @@ impl PiecewiseLinearFn {
131131

132132
fn update_envelope(&mut self) {
133133
self.recent_lines.extend(self.sorted_lines.drain(..));
134-
self.recent_lines
135-
.sort_unstable_by(|x, y| y.partial_cmp(&x).unwrap());
134+
self.recent_lines.sort_unstable_by(super::asserting_cmp);
136135
self.intersections.clear();
137136

138-
for (m1, b1) in self.recent_lines.drain(..) {
137+
for (m1, b1) in self.recent_lines.drain(..).rev() {
139138
while let Some(&(m2, b2)) = self.sorted_lines.last() {
140139
// If slopes are equal, the later line will always have lower
141140
// intercept, so we can get rid of the old one.
142-
if (m1 - m2).abs() > 1e-10f64 {
141+
if (m1 - m2).abs() > 1e-9 {
143142
let new_intersection = (b1 - b2) / (m2 - m1);
144143
if &new_intersection > self.intersections.last().unwrap_or(&f64::MIN) {
145144
self.intersections.push(new_intersection);
@@ -153,28 +152,14 @@ impl PiecewiseLinearFn {
153152
}
154153
}
155154

156-
fn eval_in_envelope(&self, x: f64) -> f64 {
157-
if self.sorted_lines.is_empty() {
158-
return f64::MAX;
159-
}
160-
let idx = match self
161-
.intersections
162-
.binary_search_by(|y| y.partial_cmp(&x).unwrap())
163-
{
164-
Ok(k) => k,
165-
Err(k) => k,
166-
};
167-
let (m, b) = self.sorted_lines[idx];
168-
m * x + b
169-
}
170-
171155
fn eval_helper(&self, x: f64) -> f64 {
172-
self.recent_lines
173-
.iter()
156+
let idx = super::slice_lower_bound(&self.intersections, &x);
157+
std::iter::once(self.sorted_lines.get(idx))
158+
.flatten()
159+
.chain(self.recent_lines.iter())
174160
.map(|&(m, b)| m * x + b)
175-
.min_by(|x, y| x.partial_cmp(y).unwrap())
176-
.unwrap_or(f64::MAX)
177-
.min(self.eval_in_envelope(x))
161+
.min_by(super::asserting_cmp)
162+
.unwrap_or(1e18)
178163
}
179164

180165
/// Evaluates the function at x
@@ -215,7 +200,7 @@ mod test {
215200
];
216201
for threshold in 0..=lines.len() {
217202
let mut func = PiecewiseLinearFn::with_merge_threshold(threshold);
218-
assert_eq!(func.evaluate(0.0), f64::MAX);
203+
assert_eq!(func.evaluate(0.0), 1e18);
219204
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
220205
func.min_with(slope as f64, intercept as f64);
221206
let ys: Vec<i64> = xs.iter().map(|&x| func.evaluate(x as f64) as i64).collect();

0 commit comments

Comments
 (0)