Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit dafd95b

Browse files
committed
Moved some algorithms to a new file misc.rs
1 parent 51416ce commit dafd95b

File tree

5 files changed

+180
-174
lines changed

5 files changed

+180
-174
lines changed

‎README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ 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, convex hull trick, and Mo's query square root decomposition
50+
- [Associative range query](src/range_query): known colloquially as *segtrees*, as well as Mo's query square root decomposition
5151
- [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
5555
- [String processing](src/string_proc.rs): Knuth-Morris-Pratt and Aho-Corasick string matching, suffix array, Manacher's linear-time palindrome search
56+
- [Miscellaneous algorithms](src/misc.rs): slice binary search, coordinate compression, convex hull trick with sqrt decomposition

‎src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Algorithms Cookbook in Rust.
22
pub mod graph;
33
pub mod math;
4+
pub mod misc;
45
pub mod range_query;
56
pub mod scanner;
67
pub mod string_proc;

‎src/misc.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//! Miscellaneous algorithms.
2+
3+
/// A comparator on partially ordered elements, that panics if they are incomparable
4+
pub fn asserting_cmp<T: PartialOrd>(a: &T, b: &T) -> std::cmp::Ordering {
5+
a.partial_cmp(b).expect("Comparing incomparable elements")
6+
}
7+
8+
/// Assuming slice is totally ordered and sorted, returns the minimum i for which
9+
/// slice[i] >= key, or slice.len() if no such i exists
10+
pub fn slice_lower_bound<T: PartialOrd>(slice: &[T], key: &T) -> usize {
11+
slice
12+
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Greater))
13+
.unwrap_err()
14+
}
15+
16+
/// Assuming slice is totally ordered and sorted, returns the minimum i for which
17+
/// slice[i] > key, or slice.len() if no such i exists
18+
pub fn slice_upper_bound<T: PartialOrd>(slice: &[T], key: &T) -> usize {
19+
slice
20+
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Less))
21+
.unwrap_err()
22+
}
23+
24+
/// A simple data structure for coordinate compression
25+
pub struct SparseIndex {
26+
coords: Vec<i64>,
27+
}
28+
29+
impl SparseIndex {
30+
/// Build an index, given the full set of coordinates to compress.
31+
pub fn new(mut coords: Vec<i64>) -> Self {
32+
coords.sort_unstable();
33+
coords.dedup();
34+
Self { coords }
35+
}
36+
37+
/// Return Ok(i) if the coordinate q appears at index i
38+
/// Return Err(i) if q appears between indices i-1 and i
39+
pub fn compress(&self, q: i64) -> Result<usize, usize> {
40+
self.coords.binary_search(&q)
41+
}
42+
}
43+
44+
/// Represents a minimum (lower envelope) of a collection of linear functions of a variable,
45+
/// evaluated using the convex hull trick with square root decomposition.
46+
pub struct PiecewiseLinearFn {
47+
sorted_lines: Vec<(f64, f64)>,
48+
intersections: Vec<f64>,
49+
recent_lines: Vec<(f64, f64)>,
50+
merge_threshold: usize,
51+
}
52+
53+
impl PiecewiseLinearFn {
54+
/// For N inserts interleaved with Q queries, a threshold of N/sqrt(Q) yields
55+
/// O(N sqrt Q + Q log N) time complexity. If all queries come after all inserts,
56+
/// any threshold less than N (e.g., 0) yields O(N + Q log N) time complexity.
57+
pub fn with_merge_threshold(merge_threshold: usize) -> Self {
58+
Self {
59+
sorted_lines: vec![],
60+
intersections: vec![],
61+
recent_lines: vec![],
62+
merge_threshold,
63+
}
64+
}
65+
66+
/// Replaces the represented function with the minimum of itself and a provided line
67+
pub fn min_with(&mut self, slope: f64, intercept: f64) {
68+
self.recent_lines.push((slope, intercept));
69+
}
70+
71+
fn update_envelope(&mut self) {
72+
self.recent_lines.extend(self.sorted_lines.drain(..));
73+
self.recent_lines.sort_unstable_by(asserting_cmp);
74+
self.intersections.clear();
75+
76+
for (new_m, new_b) in self.recent_lines.drain(..).rev() {
77+
while let Some(&(last_m, last_b)) = self.sorted_lines.last() {
78+
// If slopes are equal, get rid of the old line as its intercept is higher
79+
if (new_m - last_m).abs() > 1e-9 {
80+
let intr = (new_b - last_b) / (last_m - new_m);
81+
if self.intersections.last().map(|&x| x < intr).unwrap_or(true) {
82+
self.intersections.push(intr);
83+
break;
84+
}
85+
}
86+
self.intersections.pop();
87+
self.sorted_lines.pop();
88+
}
89+
self.sorted_lines.push((new_m, new_b));
90+
}
91+
}
92+
93+
fn eval_helper(&self, x: f64) -> f64 {
94+
let idx = slice_lower_bound(&self.intersections, &x);
95+
std::iter::once(self.sorted_lines.get(idx))
96+
.flatten()
97+
.chain(self.recent_lines.iter())
98+
.map(|&(m, b)| m * x + b)
99+
.min_by(asserting_cmp)
100+
.unwrap_or(1e18)
101+
}
102+
103+
/// Evaluates the function at x
104+
pub fn evaluate(&mut self, x: f64) -> f64 {
105+
if self.recent_lines.len() > self.merge_threshold {
106+
self.update_envelope();
107+
}
108+
self.eval_helper(x)
109+
}
110+
}
111+
112+
#[cfg(test)]
113+
mod test {
114+
use super::*;
115+
116+
#[test]
117+
fn test_bounds() {
118+
let mut vals = vec![16, 45, 45, 45, 82];
119+
120+
assert_eq!(slice_upper_bound(&vals, &44), 1);
121+
assert_eq!(slice_lower_bound(&vals, &45), 1);
122+
assert_eq!(slice_upper_bound(&vals, &45), 4);
123+
assert_eq!(slice_lower_bound(&vals, &46), 4);
124+
125+
vals.dedup();
126+
for (i, q) in vals.iter().enumerate() {
127+
assert_eq!(slice_lower_bound(&vals, q), i);
128+
assert_eq!(slice_upper_bound(&vals, q), i + 1);
129+
}
130+
}
131+
132+
#[test]
133+
fn test_coord_compress() {
134+
let mut coords = vec![16, 99, 45, 18];
135+
let index = SparseIndex::new(coords.clone());
136+
137+
coords.sort_unstable();
138+
for (i, q) in coords.into_iter().enumerate() {
139+
assert_eq!(index.compress(q - 1), Err(i));
140+
assert_eq!(index.compress(q), Ok(i));
141+
assert_eq!(index.compress(q + 1), Err(i + 1));
142+
}
143+
}
144+
145+
#[test]
146+
fn test_range_compress() {
147+
let queries = vec![(0, 10), (10, 19), (20, 29)];
148+
let coords = queries.iter().flat_map(|&(i, j)| vec![i, j + 1]).collect();
149+
let index = SparseIndex::new(coords);
150+
151+
assert_eq!(index.coords, vec![0, 10, 11, 20, 30]);
152+
}
153+
154+
#[test]
155+
fn test_convex_hull_trick() {
156+
let lines = [(0, 3), (1, 0), (-1, 8), (2, -1), (-1, 4)];
157+
let xs = [0, 1, 2, 3, 4, 5];
158+
// results[i] consists of the expected y-coordinates after processing
159+
// the first i+1 lines.
160+
let results = [
161+
[3, 3, 3, 3, 3, 3],
162+
[0, 1, 2, 3, 3, 3],
163+
[0, 1, 2, 3, 3, 3],
164+
[-1, 1, 2, 3, 3, 3],
165+
[-1, 1, 2, 1, 0, -1],
166+
];
167+
for threshold in 0..=lines.len() {
168+
let mut func = PiecewiseLinearFn::with_merge_threshold(threshold);
169+
assert_eq!(func.evaluate(0.0), 1e18);
170+
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
171+
func.min_with(slope as f64, intercept as f64);
172+
let ys: Vec<i64> = xs.iter().map(|&x| func.evaluate(x as f64) as i64).collect();
173+
assert_eq!(expected, &ys[..]);
174+
}
175+
}
176+
}
177+
}

‎src/range_query/mod.rs

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,90 +6,11 @@ pub use dynamic_arq::{ArqView, DynamicArq};
66
pub use specs::ArqSpec;
77
pub use static_arq::StaticArq;
88

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 {
17-
slice
18-
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Greater))
19-
.unwrap_err()
20-
}
21-
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 {
25-
slice
26-
.binary_search_by(|x| asserting_cmp(x, key).then(std::cmp::Ordering::Less))
27-
.unwrap_err()
28-
}
29-
30-
/// A simple data structure for coordinate compression
31-
pub struct SparseIndex {
32-
coords: Vec<i64>,
33-
}
34-
35-
impl SparseIndex {
36-
/// Build an index, given the full set of coordinates to compress.
37-
pub fn new(mut coords: Vec<i64>) -> Self {
38-
coords.sort_unstable();
39-
coords.dedup();
40-
Self { coords }
41-
}
42-
43-
/// Return Ok(i) if the coordinate q appears at index i
44-
/// Return Err(i) if q appears between indices i-1 and i
45-
pub fn compress(&self, q: i64) -> Result<usize, usize> {
46-
self.coords.binary_search(&q)
47-
}
48-
}
49-
509
#[cfg(test)]
5110
mod test {
5211
use super::specs::*;
5312
use super::*;
5413

55-
#[test]
56-
fn test_bounds() {
57-
let mut vals = vec![16, 45, 45, 45, 82];
58-
59-
assert_eq!(slice_upper_bound(&vals, &44), 1);
60-
assert_eq!(slice_lower_bound(&vals, &45), 1);
61-
assert_eq!(slice_upper_bound(&vals, &45), 4);
62-
assert_eq!(slice_lower_bound(&vals, &46), 4);
63-
64-
vals.dedup();
65-
for (i, q) in vals.iter().enumerate() {
66-
assert_eq!(slice_lower_bound(&vals, q), i);
67-
assert_eq!(slice_upper_bound(&vals, q), i + 1);
68-
}
69-
}
70-
71-
#[test]
72-
fn test_coord_compress() {
73-
let mut coords = vec![16, 99, 45, 18];
74-
let index = SparseIndex::new(coords.clone());
75-
76-
coords.sort_unstable();
77-
for (i, q) in coords.into_iter().enumerate() {
78-
assert_eq!(index.compress(q - 1), Err(i));
79-
assert_eq!(index.compress(q), Ok(i));
80-
assert_eq!(index.compress(q + 1), Err(i + 1));
81-
}
82-
}
83-
84-
#[test]
85-
fn test_range_compress() {
86-
let queries = vec![(0, 10), (10, 19), (20, 29)];
87-
let coords = queries.iter().flat_map(|&(i, j)| vec![i, j + 1]).collect();
88-
let index = SparseIndex::new(coords);
89-
90-
assert_eq!(index.coords, vec![0, 10, 11, 20, 30]);
91-
}
92-
9314
#[test]
9415
fn test_rmq() {
9516
let mut arq = StaticArq::<AssignMin>::new(&[0; 10]);

‎src/range_query/sqrt_decomp.rs

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -101,76 +101,6 @@ impl MoState for DistinctVals {
101101
}
102102
}
103103

104-
/// Represents a minimum (lower envelope) of a collection of linear functions of a variable,
105-
/// evaluated using the convex hull trick with square root decomposition.
106-
#[derive(Debug)]
107-
pub struct PiecewiseLinearFn {
108-
sorted_lines: Vec<(f64, f64)>,
109-
intersections: Vec<f64>,
110-
recent_lines: Vec<(f64, f64)>,
111-
merge_threshold: usize,
112-
}
113-
114-
impl PiecewiseLinearFn {
115-
/// For N inserts interleaved with Q queries, a threshold of N/sqrt(Q) yields
116-
/// O(N sqrt Q + Q log N) time complexity. If all queries come after all inserts,
117-
/// any threshold less than N (e.g., 0) yields O(N + Q log N) time complexity.
118-
pub fn with_merge_threshold(merge_threshold: usize) -> Self {
119-
Self {
120-
sorted_lines: vec![],
121-
intersections: vec![],
122-
recent_lines: vec![],
123-
merge_threshold,
124-
}
125-
}
126-
127-
/// Replaces this function with the minimum of itself and a provided line
128-
pub fn min_with(&mut self, slope: f64, intercept: f64) {
129-
self.recent_lines.push((slope, intercept));
130-
}
131-
132-
fn update_envelope(&mut self) {
133-
self.recent_lines.extend(self.sorted_lines.drain(..));
134-
self.recent_lines.sort_unstable_by(super::asserting_cmp);
135-
self.intersections.clear();
136-
137-
for (m1, b1) in self.recent_lines.drain(..).rev() {
138-
while let Some(&(m2, b2)) = self.sorted_lines.last() {
139-
// If slopes are equal, the later line will always have lower
140-
// intercept, so we can get rid of the old one.
141-
if (m1 - m2).abs() > 1e-9 {
142-
let new_intersection = (b1 - b2) / (m2 - m1);
143-
if &new_intersection > self.intersections.last().unwrap_or(&f64::MIN) {
144-
self.intersections.push(new_intersection);
145-
break;
146-
}
147-
}
148-
self.intersections.pop();
149-
self.sorted_lines.pop();
150-
}
151-
self.sorted_lines.push((m1, b1));
152-
}
153-
}
154-
155-
fn eval_helper(&self, x: f64) -> f64 {
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())
160-
.map(|&(m, b)| m * x + b)
161-
.min_by(super::asserting_cmp)
162-
.unwrap_or(1e18)
163-
}
164-
165-
/// Evaluates the function at x
166-
pub fn evaluate(&mut self, x: f64) -> f64 {
167-
if self.recent_lines.len() > self.merge_threshold {
168-
self.update_envelope();
169-
}
170-
self.eval_helper(x)
171-
}
172-
}
173-
174104
#[cfg(test)]
175105
mod test {
176106
use super::*;
@@ -184,28 +114,4 @@ mod test {
184114

185115
assert_eq!(answers, vec![2, 1, 5, 5]);
186116
}
187-
188-
#[test]
189-
fn test_convex_hull_trick() {
190-
let lines = [(0, 3), (1, 0), (-1, 8), (2, -1), (-1, 4)];
191-
let xs = [0, 1, 2, 3, 4, 5];
192-
// results[i] consists of the expected y-coordinates after processing
193-
// the first i+1 lines.
194-
let results = [
195-
[3, 3, 3, 3, 3, 3],
196-
[0, 1, 2, 3, 3, 3],
197-
[0, 1, 2, 3, 3, 3],
198-
[-1, 1, 2, 3, 3, 3],
199-
[-1, 1, 2, 1, 0, -1],
200-
];
201-
for threshold in 0..=lines.len() {
202-
let mut func = PiecewiseLinearFn::with_merge_threshold(threshold);
203-
assert_eq!(func.evaluate(0.0), 1e18);
204-
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
205-
func.min_with(slope as f64, intercept as f64);
206-
let ys: Vec<i64> = xs.iter().map(|&x| func.evaluate(x as f64) as i64).collect();
207-
assert_eq!(expected, &ys[..]);
208-
}
209-
}
210-
}
211117
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /