pub fn alpha_prune(
candidate_distances: &[(u32, f32)],
target: u32,
r: usize,
alpha: f32,
pairwise_dist: impl Fn(u32, u32) -> f32,
) -> Vec<u32> {
let active: Vec<(u32, f32)> = candidate_distances.to_vec();
let mut result: Vec<u32> = Vec::with_capacity(r);
let mut dropped: Vec<bool> = vec![false; active.len()];
while result.len() < r {
let Some(star_pos) = active
.iter()
.enumerate()
.find(|(i, _)| !dropped[*i])
.map(|(i, _)| i)
else {
break; };
let (p_star, dist_star_to_target) = active[star_pos];
if p_star == target {
dropped[star_pos] = true;
continue;
}
result.push(p_star);
dropped[star_pos] = true;
for i in 0..active.len() {
if dropped[i] {
continue;
}
let (p_prime, dist_prime_to_target) = active[i];
if p_prime == target {
dropped[i] = true;
continue;
}
let _ = dist_star_to_target; let d_star_prime = pairwise_dist(p_star, p_prime);
if alpha * d_star_prime <= dist_prime_to_target {
dropped[i] = true;
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn dist1d(a: u32, b: u32) -> f32 {
let fa = a as f32;
let fb = b as f32;
(fa - fb).abs()
}
#[test]
fn preserves_closest_candidate() {
let target: u32 = 100;
let candidates: Vec<(u32, f32)> = vec![
(101, 1.0), (105, 5.0), (109, 9.0), ];
let result = alpha_prune(&candidates, target, 3, 1.2, dist1d);
assert!(result.contains(&101), "closest candidate must be retained");
}
#[test]
fn drops_mutually_close_candidates_with_alpha_1_2() {
let target: u32 = 0;
let candidates: Vec<(u32, f32)> = vec![(1, 1.0), (2, 2.0), (100, 100.0)];
let result = alpha_prune(&candidates, target, 3, 1.2, dist1d);
assert!(result.contains(&1));
assert!(
!result.contains(&2),
"node 2 should be pruned (close to p*=1)"
);
assert!(result.contains(&100), "node 100 retained (far from p*=1)");
}
#[test]
fn respects_degree_bound() {
let target: u32 = 1000;
let candidates: Vec<(u32, f32)> =
(0u32..20).map(|i| (i, (i as f32 + 1.0) * 100.0)).collect();
let result = alpha_prune(&candidates, target, 5, 1.2, |_a, _b| 0.001);
assert!(result.len() <= 5, "result must be capped at r=5");
}
#[test]
fn empty_candidates_returns_empty() {
let result = alpha_prune(&[], 0, 4, 1.2, |_a, _b| 0.0);
assert!(result.is_empty());
}
}