fix(math/cyclic_search): Use classic binary search instead

It is unnecessary to use exponential search here, and the
computation complexity of binary search is easier to understand
and obvious O(log n).
This degrades performance minimally in biased edge cases,
and improves performance in the hopefully common
"roughly half of values are false, and other half is true" case.
This commit is contained in:
Ellen Emilia Anna Zscheile 2025-05-30 15:06:49 +02:00
parent 864cf9085a
commit 42cf8f3a69
1 changed files with 15 additions and 22 deletions

View File

@ -129,7 +129,7 @@ where
} }
/// Search for the largest index inside the bounds which still fulfills the condition /// Search for the largest index inside the bounds which still fulfills the condition
fn exponential_search<T, EF>( fn binary_search<T, EF>(
eval: &EF, eval: &EF,
expected_value: T, expected_value: T,
mut bounds: core::ops::Range<usize>, mut bounds: core::ops::Range<usize>,
@ -143,28 +143,21 @@ where
return None; return None;
} }
let mut largest_checked = bounds.start; while bounds.start + 1 < bounds.end {
while (bounds.start + 1) < bounds.end { let middle = bounds.start + (bounds.end - bounds.start) / 2;
let len = bounds.end - bounds.start; debug_assert_ne!(middle, bounds.start);
for level in 0..64u8 { debug_assert_ne!(middle, bounds.end);
let mut index = 1 << level;
if index >= len { // binary search for partition bounds
break; if eval(middle) == expected_value {
bounds.start = middle;
} else {
bounds.end = middle;
} }
index += bounds.start;
if eval(index) != expected_value {
bounds.end = index;
break;
}
largest_checked = index;
} }
bounds.start = largest_checked; debug_assert_eq!(eval(bounds.start), expected_value);
// this implies that `bounds.start` doesn't have to get checked again Some(bounds.start)
}
debug_assert_eq!(eval(largest_checked), expected_value);
Some(largest_checked)
} }
/// Perform a breadth-first search on an induced binary tree on the list, /// Perform a breadth-first search on an induced binary tree on the list,
@ -221,8 +214,8 @@ where
true => (&mut pos_true, &mut pos_false), true => (&mut pos_true, &mut pos_false),
}; };
*pos_start = exponential_search(&eval, val_start, bounds.start..*pos_next).unwrap(); *pos_start = binary_search(&eval, val_start, bounds.start..*pos_next).unwrap();
*pos_next = exponential_search(&eval, !val_start, *pos_start + 1..bounds.end).unwrap(); *pos_next = binary_search(&eval, !val_start, *pos_start + 1..bounds.end).unwrap();
} }
(Some(pos_false), Some(pos_true)) (Some(pos_false), Some(pos_true))