use core::cmp::Reverse;
use crate::float::Ordf32;
use super::CoinSelector;
use alloc::collections::BinaryHeap;
#[derive(Debug)]
pub(crate) struct BnbIter<'a, M: BnbMetric> {
queue: BinaryHeap<Branch<'a>>,
best: Option<Ordf32>,
metric: M,
}
impl<'a, M: BnbMetric> Iterator for BnbIter<'a, M> {
type Item = Option<(CoinSelector<'a>, Ordf32)>;
fn next(&mut self) -> Option<Self::Item> {
let branch = self.queue.pop()?;
if let Some(best) = &self.best {
if *best < branch.lower_bound {
return None;
}
}
let selector = branch.selector;
self.insert_new_branches(&selector);
if branch.is_exclusion {
return Some(None);
}
let score = match self.metric.score(&selector) {
Some(score) => score,
None => return Some(None),
};
if let Some(best_score) = &self.best {
if score >= *best_score {
return Some(None);
}
}
self.best = Some(score);
Some(Some((selector, score)))
}
}
impl<'a, M: BnbMetric> BnbIter<'a, M> {
pub(crate) fn new(mut selector: CoinSelector<'a>, metric: M) -> Self {
let mut iter = BnbIter {
queue: BinaryHeap::default(),
best: None,
metric,
};
if iter.metric.requires_ordering_by_descending_value_pwu() {
selector.sort_candidates_by_descending_value_pwu();
}
iter.consider_adding_to_queue(&selector, false);
iter
}
fn consider_adding_to_queue(&mut self, cs: &CoinSelector<'a>, is_exclusion: bool) {
let bound = self.metric.bound(cs);
if let Some(bound) = bound {
if self.best.is_none() || self.best.as_ref().unwrap() >= &bound {
let branch = Branch {
lower_bound: bound,
selector: cs.clone(),
is_exclusion,
};
self.queue.push(branch);
}
}
}
fn insert_new_branches(&mut self, cs: &CoinSelector<'a>) {
let (next_index, next) = match cs.unselected().next() {
Some(c) => c,
None => return, };
let mut inclusion_cs = cs.clone();
inclusion_cs.select(next_index);
self.consider_adding_to_queue(&inclusion_cs, false);
let mut is_first_ban = true;
let mut exclusion_cs = cs.clone();
let to_ban = (next.value, next.weight);
for (next_index, next) in cs.unselected() {
if (next.value, next.weight) != to_ban {
break;
}
let (_index, _candidate) = exclusion_cs
.candidates()
.find(|(i, _)| *i == next_index)
.expect("must have index since we are planning to ban it");
if is_first_ban {
is_first_ban = false;
}
exclusion_cs.ban(next_index);
}
self.consider_adding_to_queue(&exclusion_cs, true);
}
}
#[derive(Debug, Clone)]
struct Branch<'a> {
lower_bound: Ordf32,
selector: CoinSelector<'a>,
is_exclusion: bool,
}
impl<'a> Ord for Branch<'a> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
core::cmp::Ord::cmp(
&(Reverse(&self.lower_bound), !self.is_exclusion),
&(Reverse(&other.lower_bound), !other.is_exclusion),
)
}
}
impl<'a> PartialOrd for Branch<'a> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'a> PartialEq for Branch<'a> {
fn eq(&self, other: &Self) -> bool {
self.lower_bound == other.lower_bound
}
}
impl<'a> Eq for Branch<'a> {}
pub trait BnbMetric {
fn score(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32>;
fn bound(&mut self, cs: &CoinSelector<'_>) -> Option<Ordf32>;
fn requires_ordering_by_descending_value_pwu(&self) -> bool {
false
}
}