use super::feerate_key::FeerateTransactionKey;
use std::iter::FusedIterator;
use sweep_bptree::tree::visit::{DescendVisit, DescendVisitResult};
use sweep_bptree::tree::{Argument, SearchArgument};
use sweep_bptree::{BPlusTree, NodeStoreVec};
type FeerateKey = FeerateTransactionKey;
#[derive(Clone, Copy, Debug, Default)]
struct FeerateWeight(f64);
impl FeerateWeight {
pub fn weight(&self) -> f64 {
self.0
}
}
impl Argument<FeerateKey> for FeerateWeight {
fn from_leaf(keys: &[FeerateKey]) -> Self {
Self(keys.iter().map(|k| k.weight()).sum())
}
fn from_inner(_keys: &[FeerateKey], arguments: &[Self]) -> Self {
Self(arguments.iter().map(|a| a.0).sum())
}
}
impl SearchArgument<FeerateKey> for FeerateWeight {
type Query = f64;
fn locate_in_leaf(query: Self::Query, keys: &[FeerateKey]) -> Option<usize> {
let mut sum = 0.0;
for (i, k) in keys.iter().enumerate() {
let w = k.weight();
sum += w;
if query < sum {
return Some(i);
}
}
match keys.len() {
0 => None,
#[allow(clippy::arithmetic_side_effects, reason = "The preceding arm handles zero, so n >= 1.")]
n => Some(n - 1),
}
}
fn locate_in_inner(mut query: Self::Query, _keys: &[FeerateKey], arguments: &[Self]) -> Option<(usize, Self::Query)> {
for (i, a) in arguments.iter().enumerate() {
if query >= a.0 {
query -= a.0;
} else {
return Some((i, query));
}
}
match arguments.len() {
0 => None,
#[allow(clippy::arithmetic_side_effects, reason = "The preceding arm handles zero, so both n - 1 expressions are safe.")]
n => Some((n - 1, arguments[n - 1].0)),
}
}
}
struct PrefixWeightVisitor<'a> {
key: &'a FeerateKey,
accumulated_weight: f64,
}
impl<'a> PrefixWeightVisitor<'a> {
pub fn new(key: &'a FeerateKey) -> Self {
Self { key, accumulated_weight: Default::default() }
}
fn search_in_keys(&self, keys: &[FeerateKey]) -> usize {
match keys.binary_search(self.key) {
Err(idx) => {
idx
}
Ok(idx) => {
#[allow(clippy::arithmetic_side_effects, reason = "ARITH-SAFETY(INDEX)")]
{
idx + 1
}
}
}
}
}
impl DescendVisit<FeerateKey, (), FeerateWeight> for PrefixWeightVisitor<'_> {
type Result = f64;
fn visit_inner(&mut self, keys: &[FeerateKey], arguments: &[FeerateWeight]) -> DescendVisitResult<Self::Result> {
let idx = self.search_in_keys(keys);
for argument in arguments.iter().take(idx) {
self.accumulated_weight += argument.weight();
}
DescendVisitResult::GoDown(idx)
}
fn visit_leaf(&mut self, keys: &[FeerateKey], _values: &[()]) -> Option<Self::Result> {
let idx = self.search_in_keys(keys);
for key in keys.iter().take(idx) {
self.accumulated_weight += key.weight();
}
Some(self.accumulated_weight)
}
}
type InnerTree = BPlusTree<NodeStoreVec<FeerateKey, (), FeerateWeight>>;
pub struct SearchTree {
tree: InnerTree,
}
impl Default for SearchTree {
fn default() -> Self {
Self { tree: InnerTree::new(Default::default()) }
}
}
impl Clone for SearchTree {
fn clone(&self) -> Self {
Self { tree: InnerTree::bulk_load(self.ascending_iter().cloned().map(|key| (key, ())).collect()) }
}
}
impl SearchTree {
pub fn new() -> Self {
Self { tree: InnerTree::new(Default::default()) }
}
pub fn len(&self) -> usize {
self.tree.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn insert(&mut self, key: FeerateKey) -> bool {
self.tree.insert(key, ()).is_none()
}
pub fn remove(&mut self, key: &FeerateKey) -> bool {
self.tree.remove(key).is_some()
}
pub fn search(&self, query: f64) -> &FeerateKey {
self.tree.get_by_argument(query).expect("clamped").0
}
pub fn remove_by_query(&mut self, query: f64) -> FeerateKey {
self.tree.remove_by_argument(query).expect("clamped").0
}
pub fn total_weight(&self) -> f64 {
self.tree.root_argument().weight()
}
pub fn prefix_weight(&self, key: &FeerateKey) -> f64 {
self.tree.descend_visit(PrefixWeightVisitor::new(key)).unwrap()
}
pub fn descending_iter(&self) -> impl DoubleEndedIterator<Item = &FeerateKey> + ExactSizeIterator + FusedIterator {
self.tree.iter().rev().map(|(key, ())| key)
}
pub fn ascending_iter(&self) -> impl DoubleEndedIterator<Item = &FeerateKey> + ExactSizeIterator + FusedIterator {
self.tree.iter().map(|(key, ())| key)
}
pub fn first(&self) -> Option<&FeerateKey> {
self.tree.first().map(|(k, ())| k)
}
pub fn last(&self) -> Option<&FeerateKey> {
self.tree.last().map(|(k, ())| k)
}
}
#[cfg(test)]
mod tests {
use super::super::feerate_key::tests::build_feerate_key;
use super::*;
use itertools::Itertools;
use std::collections::HashSet;
use std::ops::Sub;
#[test]
fn test_feerate_weight_queries() {
let mut tree = SearchTree::new();
let mass = 2000;
let fees = vec![[123, 113, 10_000, 1000, 2050, 2048]; 64 * (64 + 1)].into_iter().flatten().collect_vec();
#[allow(clippy::mutable_key_type)]
let mut s = HashSet::with_capacity(fees.len());
for (i, fee) in fees.iter().copied().enumerate() {
let key = build_feerate_key(fee, mass, i as u64);
s.insert(key.clone());
tree.insert(key);
}
let remove = s.iter().take(fees.len() / 6).cloned().collect_vec();
for r in remove {
s.remove(&r);
tree.remove(&r);
}
let mut v = s.into_iter().collect_vec();
v.sort();
for (expected, item) in v.iter().rev().zip(tree.descending_iter()) {
assert_eq!(&expected, &item);
assert!(expected.cmp(item).is_eq()); }
let eps: f64 = 0.001;
let mut sum = 0.0;
for expected in v.iter() {
let weight = expected.weight();
let eps = eps.min(weight / 3.0);
let samples = [sum + eps, sum + weight / 2.0, sum + weight - eps];
for sample in samples {
let key = tree.search(sample);
assert_eq!(expected, key);
assert!(expected.cmp(key).is_eq()); }
sum += weight;
}
println!("{}, {}", sum, tree.total_weight());
assert_eq!(tree.first(), Some(tree.search(f64::NEG_INFINITY)));
assert_eq!(tree.first(), Some(tree.search(-1.0)));
assert_eq!(tree.first(), Some(tree.search(-eps)));
assert_eq!(tree.first(), Some(tree.search(0.0)));
assert_eq!(tree.last(), Some(tree.search(sum)));
assert_eq!(tree.last(), Some(tree.search(sum + eps)));
assert_eq!(tree.last(), Some(tree.search(sum + 1.0)));
assert_eq!(tree.last(), Some(tree.search(1.0 / 0.0)));
assert_eq!(tree.last(), Some(tree.search(f64::INFINITY)));
let _ = tree.search(f64::NAN);
let mut prefix = Vec::with_capacity(v.len());
prefix.push(v[0].weight());
for i in 1..v.len() {
prefix.push(prefix[i - 1] + v[i].weight());
}
let eps = v.iter().map(|k| k.weight()).min_by(f64::total_cmp).unwrap() * 1e-4;
for (expected_prefix, key) in prefix.into_iter().zip(v) {
let prefix = tree.prefix_weight(&key);
assert!(expected_prefix.sub(prefix).abs() < eps);
}
}
#[test]
fn test_tree_rev_iter() {
let mut tree = SearchTree::new();
let mass = 2000;
let fees = vec![[123, 113, 10_000, 1000, 2050, 2048]; 64 * (64 + 1)].into_iter().flatten().collect_vec();
let mut v = Vec::with_capacity(fees.len());
for (i, fee) in fees.iter().copied().enumerate() {
let key = build_feerate_key(fee, mass, i as u64);
v.push(key.clone());
tree.insert(key);
}
v.sort();
for (expected, item) in v.into_iter().rev().zip(tree.descending_iter()) {
assert_eq!(&expected, item);
assert!(expected.cmp(item).is_eq()); }
}
#[test]
fn test_remove_by_query_removes_searched_key() {
let mut tree = SearchTree::new();
let keys = (0..200).map(|i| build_feerate_key(i + 1, 1650, i)).collect_vec();
for key in keys.iter().cloned() {
tree.insert(key);
}
let query = tree.total_weight() / 2.0;
let expected = tree.search(query).clone();
let removed = tree.remove_by_query(query);
assert_eq!(removed, expected);
assert!(!tree.ascending_iter().any(|candidate| candidate == &removed));
assert_eq!(tree.len(), keys.len() - 1);
}
#[test]
fn test_clone_preserves_order_and_weight_queries() {
for size in [1, 2, 63, 64, 65, 4096, 4097] {
let mut tree = SearchTree::new();
for i in 0..size {
let fee = ((i * 1_103 + size * 37) % 1_000_000 + 1) as u64;
let mass = ((i * 97 + size * 13) % 10_000 + 1_000) as u64;
tree.insert(build_feerate_key(fee, mass, i as u64));
}
let cloned = tree.clone();
let original_keys = tree.ascending_iter().cloned().collect_vec();
let cloned_keys = cloned.ascending_iter().cloned().collect_vec();
assert_eq!(original_keys, cloned_keys, "size {size}");
assert_eq!(tree.len(), cloned.len(), "size {size}");
let total_weight = original_keys.iter().map(|key| key.weight()).sum::<f64>();
let eps = total_weight * 1e-10;
assert!(tree.total_weight().sub(cloned.total_weight()).abs() < eps, "size {size}");
let mut prefix = 0.0;
let stride = (size / 32).max(1);
for (idx, key) in original_keys.iter().enumerate() {
let sample = prefix + key.weight() / 2.0;
prefix += key.weight();
if idx % stride == 0 {
assert_eq!(tree.search(sample), cloned.search(sample), "size {size}, idx {idx}");
assert!(tree.prefix_weight(key).sub(cloned.prefix_weight(key)).abs() < eps, "size {size}, idx {idx}");
}
}
}
}
#[test]
fn test_clone_is_independent_after_mutation() {
let mut tree = SearchTree::new();
let keys = (0..200).map(|i| build_feerate_key(i + 1, 1650, i)).collect_vec();
for key in keys.iter().cloned() {
tree.insert(key);
}
let mut cloned = tree.clone();
for key in keys.iter().take(50) {
cloned.remove(key);
}
for key in keys.iter().skip(200 - 25) {
tree.remove(key);
}
assert_eq!(tree.len(), 175);
assert_eq!(cloned.len(), 150);
for key in keys.iter().take(50) {
assert!(tree.ascending_iter().any(|candidate| candidate == key));
assert!(!cloned.ascending_iter().any(|candidate| candidate == key));
}
}
}