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 bf762c9

Browse files
committed
Remove merge_threshold parameter and add proof
1 parent 5ca0294 commit bf762c9

File tree

1 file changed

+28
-29
lines changed

1 file changed

+28
-29
lines changed

‎src/order.rs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ pub fn slice_upper_bound<T: PartialOrd>(slice: &[T], key: &T) -> usize {
2121
.unwrap_err()
2222
}
2323

24-
/// Merge two sorted and totally ordered collections into one
24+
/// Stably merges two sorted and totally ordered collections into one
2525
pub fn merge_sorted<T: PartialOrd>(
2626
i1: impl IntoIterator<Item = T>,
2727
i2: impl IntoIterator<Item = T>,
2828
) -> Vec<T> {
29-
let (mut i1, mut i2) = (i1.into_iter().peekable(), i2.into_iter().peekable());
29+
let mut i1 = i1.into_iter().peekable();
30+
let mut i2 = i2.into_iter().peekable();
3031
let mut merged = Vec::with_capacity(i1.size_hint().0 + i2.size_hint().0);
3132
while let (Some(a), Some(b)) = (i1.peek(), i2.peek()) {
3233
merged.push(if a <= b { i1.next() } else { i2.next() }.unwrap());
@@ -51,15 +52,15 @@ pub struct SparseIndex {
5152
}
5253

5354
impl SparseIndex {
54-
/// Build an index, given the full set of coordinates to compress.
55+
/// Builds an index, given the full set of coordinates to compress.
5556
pub fn new(mut coords: Vec<i64>) -> Self {
5657
coords.sort_unstable();
5758
coords.dedup();
5859
Self { coords }
5960
}
6061

61-
/// Return Ok(i) if the coordinate q appears at index i
62-
/// Return Err(i) if q appears between indices i-1 and i
62+
/// Returns Ok(i) if the coordinate q appears at index i
63+
/// Returns Err(i) if q appears between indices i-1 and i
6364
pub fn compress(&self, q: i64) -> Result<usize, usize> {
6465
self.coords.binary_search(&q)
6566
}
@@ -68,28 +69,26 @@ impl SparseIndex {
6869
/// Represents a maximum (upper envelope) of a collection of linear functions of one
6970
/// variable, evaluated using an online version of the convex hull trick.
7071
/// It combines the offline algorithm with square root decomposition, resulting in an
71-
/// asymptotically suboptimal but simple algorithm with good amortized performnce:
72-
/// For N inserts interleaved with Q queries, a threshold of N/sqrt(Q) yields
73-
/// O(N sqrt Q + Q log N) time complexity. If all queries come after all inserts,
74-
/// any threshold less than N (e.g., 0) yields O(N + Q log N) time complexity.
72+
/// asymptotically suboptimal but simple algorithm with good amortized performance:
73+
/// N inserts interleaved with Q queries yields O(N sqrt Q + Q log N) time complexity
74+
/// in general, or O(N + Q log N) if all queries come after all inserts.
75+
// Proof: the Q log N term comes from calls to slice_lower_bound(). As for the N sqrt Q,
76+
// note that between successive times when the hull is rebuilt, O(N) work is done,
77+
// and the running totals of insertions and queries satisfy del_N (del_Q + 1) > N.
78+
// Now, either del_Q >= sqrt Q, or else del_Q <= 2 sqrt Q - 1
79+
// => del_N > N / (2 sqrt Q).
80+
// Since del(N sqrt Q) >= max(N del(sqrt Q), del_N sqrt Q)
81+
// >= max(N del_Q / (2 sqrt Q), del_N sqrt Q),
82+
// we conclude that del(N sqrt Q) >= N / 2.
83+
#[derive(Default)]
7584
pub struct PiecewiseLinearConvexFn {
7685
recent_lines: Vec<(f64, f64)>,
7786
sorted_lines: Vec<(f64, f64)>,
7887
intersections: Vec<f64>,
79-
merge_threshold: usize,
88+
amortized_work: usize,
8089
}
8190

8291
impl PiecewiseLinearConvexFn {
83-
/// Initializes with a given threshold for re-running the convex hull algorithm
84-
pub fn with_merge_threshold(merge_threshold: usize) -> Self {
85-
Self {
86-
recent_lines: vec![],
87-
sorted_lines: vec![],
88-
intersections: vec![],
89-
merge_threshold,
90-
}
91-
}
92-
9392
/// Replaces the represented function with the maximum of itself and a provided line
9493
pub fn max_with(&mut self, new_m: f64, new_b: f64) {
9594
self.recent_lines.push((new_m, new_b));
@@ -125,7 +124,9 @@ impl PiecewiseLinearConvexFn {
125124

126125
/// Evaluates the function at x with good amortized runtime
127126
pub fn evaluate(&mut self, x: f64) -> f64 {
128-
if self.recent_lines.len() > self.merge_threshold {
127+
self.amortized_work += self.recent_lines.len();
128+
if self.amortized_work > self.sorted_lines.len() {
129+
self.amortized_work = 0;
129130
self.recent_lines.sort_unstable_by(asserting_cmp);
130131
self.intersections.clear();
131132
let all_lines = merge_sorted(self.recent_lines.drain(..), self.sorted_lines.drain(..));
@@ -212,14 +213,12 @@ mod test {
212213
[1, -1, -2, -3, -3, -3],
213214
[1, -1, -2, -1, 0, 1],
214215
];
215-
for threshold in 0..=lines.len() {
216-
let mut func = PiecewiseLinearConvexFn::with_merge_threshold(threshold);
217-
assert_eq!(func.evaluate(0.0), -1e18);
218-
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
219-
func.max_with(slope as f64, intercept as f64);
220-
let ys: Vec<i64> = xs.iter().map(|&x| func.evaluate(x as f64) as i64).collect();
221-
assert_eq!(expected, &ys[..]);
222-
}
216+
let mut func = PiecewiseLinearConvexFn::default();
217+
assert_eq!(func.evaluate(0.0), -1e18);
218+
for (&(slope, intercept), expected) in lines.iter().zip(results.iter()) {
219+
func.max_with(slope as f64, intercept as f64);
220+
let ys: Vec<i64> = xs.iter().map(|&x| func.evaluate(x as f64) as i64).collect();
221+
assert_eq!(expected, &ys[..]);
223222
}
224223
}
225224
}

0 commit comments

Comments
(0)

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