Files
oak-gpui/crates/sum_tree/src/sum_tree.rs
T
Lukas Wirth e1b57f00a0 sum_tree: Reduce Cursor size for contextless summary types (#38776)
This reduces the size of cursor by a usize when the summary does not
require a context making Cursor usages and constructions slightly more
efficient.

This change is a bit annoying though, as Rust has no means of
specializing, so this uses a `ContextlessSummary` trait with a blanket
impl while turning the `Context` into a GAT `Context<'a>`. This means
`Summary` implies are a bit more verbose now while contextless ones are
slimmer. It does come with the downside that the lifetime in the GAT is
always considered invariant, so some lifetime splitting occurred due to
that.


 ```
push/4096               time:   [352.65 µs 360.87 µs 367.80 µs]
                        thrpt:  [10.621 MiB/s 10.825 MiB/s 11.077 MiB/s]
                 change:
time: [-2.6633% -1.3640% -0.0561%] (p = 0.05 < 0.05)
                        thrpt:  [+0.0561% +1.3828% +2.7361%]
                        Change within noise threshold.
Found 16 outliers among 100 measurements (16.00%)
  7 (7.00%) low severe
  3 (3.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe
push/65536              time:   [1.2917 ms 1.2949 ms 1.2979 ms]
                        thrpt:  [48.156 MiB/s 48.267 MiB/s 48.387 MiB/s]
                 change:
time: [+1.4428% +1.9844% +2.5299%] (p = 0.00 < 0.05)
                        thrpt:  [-2.4675% -1.9458% -1.4223%]
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low severe
  1 (1.00%) low mild
  1 (1.00%) high severe

append/4096             time:   [677.87 ns 678.87 ns 679.83 ns]
                        thrpt:  [5.6112 GiB/s 5.6192 GiB/s 5.6274 GiB/s]
                 change:
time: [-0.8924% -0.5017% -0.1705%] (p = 0.00 < 0.05)
                        thrpt:  [+0.1708% +0.5043% +0.9004%]
                        Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
append/65536            time:   [9.3275 µs 9.3406 µs 9.3536 µs]
                        thrpt:  [6.5253 GiB/s 6.5344 GiB/s 6.5435 GiB/s]
                 change:
time: [+0.5409% +0.7215% +0.9054%] (p = 0.00 < 0.05)
                        thrpt:  [-0.8973% -0.7163% -0.5380%]
                        Change within noise threshold.

slice/4096              time:   [27.673 µs 27.791 µs 27.907 µs]
                        thrpt:  [139.97 MiB/s 140.56 MiB/s 141.16 MiB/s]
                 change:
time: [-1.1065% -0.6725% -0.2429%] (p = 0.00 < 0.05)
                        thrpt:  [+0.2435% +0.6770% +1.1189%]
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  4 (4.00%) low mild
  1 (1.00%) high mild
slice/65536             time:   [507.55 µs 517.40 µs 535.60 µs]
                        thrpt:  [116.69 MiB/s 120.80 MiB/s 123.14 MiB/s]
                 change:
time: [-1.3489% +0.0599% +2.2591%] (p = 0.96 > 0.05)
                        thrpt:  [-2.2092% -0.0598% +1.3674%]
                        No change in performance detected.
Found 8 outliers among 100 measurements (8.00%)
  5 (5.00%) low mild
  2 (2.00%) high mild
  1 (1.00%) high severe

bytes_in_range/4096     time:   [3.3917 µs 3.4108 µs 3.4313 µs]
                        thrpt:  [1.1117 GiB/s 1.1184 GiB/s 1.1247 GiB/s]
                 change:
time: [-5.3466% -4.7193% -4.1262%] (p = 0.00 < 0.05)
                        thrpt:  [+4.3038% +4.9531% +5.6487%]
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  1 (1.00%) low mild
  5 (5.00%) high mild
bytes_in_range/65536    time:   [88.175 µs 88.613 µs 89.111 µs]
                        thrpt:  [701.37 MiB/s 705.31 MiB/s 708.82 MiB/s]
                 change:
time: [-0.6935% +0.3769% +1.4655%] (p = 0.50 > 0.05)
                        thrpt:  [-1.4443% -0.3755% +0.6984%]
                        No change in performance detected.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

chars/4096              time:   [678.70 ns 680.38 ns 682.08 ns]
                        thrpt:  [5.5927 GiB/s 5.6067 GiB/s 5.6206 GiB/s]
                 change:
time: [-0.6969% -0.2755% +0.1485%] (p = 0.20 > 0.05)
                        thrpt:  [-0.1483% +0.2763% +0.7018%]
                        No change in performance detected.
Found 9 outliers among 100 measurements (9.00%)
  5 (5.00%) low mild
  4 (4.00%) high mild
chars/65536             time:   [12.720 µs 12.775 µs 12.830 µs]
                        thrpt:  [4.7573 GiB/s 4.7778 GiB/s 4.7983 GiB/s]
                 change:
time: [-0.6172% -0.1110% +0.4179%] (p = 0.68 > 0.05)
                        thrpt:  [-0.4162% +0.1112% +0.6211%]
                        No change in performance detected.
Found 2 outliers among 100 measurements (2.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild

clip_point/4096         time:   [33.240 µs 33.310 µs 33.394 µs]
                        thrpt:  [116.98 MiB/s 117.27 MiB/s 117.52 MiB/s]
                 change:
time: [-2.8892% -2.6305% -2.3438%] (p = 0.00 < 0.05)
                        thrpt:  [+2.4000% +2.7015% +2.9751%]
                        Performance has improved.
Found 12 outliers among 100 measurements (12.00%)
  1 (1.00%) low mild
  4 (4.00%) high mild
  7 (7.00%) high severe
clip_point/65536        time:   [1.6531 ms 1.6586 ms 1.6640 ms]
                        thrpt:  [37.560 MiB/s 37.683 MiB/s 37.808 MiB/s]
                 change:
time: [-6.6381% -5.9395% -5.2680%] (p = 0.00 < 0.05)
                        thrpt:  [+5.5610% +6.3146% +7.1100%]
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe

point_to_offset/4096    time:   [11.586 µs 11.603 µs 11.621 µs]
                        thrpt:  [336.15 MiB/s 336.67 MiB/s 337.16 MiB/s]
                 change:
time: [-14.289% -14.111% -13.939%] (p = 0.00 < 0.05)
                        thrpt:  [+16.197% +16.429% +16.672%]
                        Performance has improved.
Found 12 outliers among 100 measurements (12.00%)
  3 (3.00%) low severe
  5 (5.00%) low mild
  4 (4.00%) high mild
point_to_offset/65536   time:   [527.74 µs 532.08 µs 536.51 µs]
                        thrpt:  [116.49 MiB/s 117.46 MiB/s 118.43 MiB/s]
                 change:
time: [-6.7825% -4.6235% -2.3533%] (p = 0.00 < 0.05)
                        thrpt:  [+2.4100% +4.8477% +7.2760%]
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  4 (4.00%) high mild
  4 (4.00%) high severe

cursor/4096             time:   [16.154 µs 16.192 µs 16.232 µs]
                        thrpt:  [240.66 MiB/s 241.24 MiB/s 241.81 MiB/s]
                 change:
time: [-3.2536% -2.9145% -2.5526%] (p = 0.00 < 0.05)
                        thrpt:  [+2.6194% +3.0019% +3.3630%]
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low mild
  2 (2.00%) high mild
  2 (2.00%) high severe
cursor/65536            time:   [509.60 µs 511.24 µs 512.93 µs]
                        thrpt:  [121.85 MiB/s 122.25 MiB/s 122.65 MiB/s]
                 change:
time: [-7.3677% -6.6017% -5.7840%] (p = 0.00 < 0.05)
                        thrpt:  [+6.1391% +7.0683% +7.9537%]
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  3 (3.00%) high mild
  3 (3.00%) high severe
```
Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-24 14:35:38 +02:00

1445 lines
48 KiB
Rust

mod cursor;
mod tree_map;
use arrayvec::ArrayVec;
pub use cursor::{Cursor, FilterCursor, Iter};
use rayon::prelude::*;
use std::marker::PhantomData;
use std::mem;
use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};
pub use tree_map::{MapSeekTarget, TreeMap, TreeSet};
#[cfg(test)]
pub const TREE_BASE: usize = 2;
#[cfg(not(test))]
pub const TREE_BASE: usize = 6;
/// An item that can be stored in a [`SumTree`]
///
/// Must be summarized by a type that implements [`Summary`]
pub trait Item: Clone {
type Summary: Summary;
fn summary(&self, cx: <Self::Summary as Summary>::Context<'_>) -> Self::Summary;
}
/// An [`Item`] whose summary has a specific key that can be used to identify it
pub trait KeyedItem: Item {
type Key: for<'a> Dimension<'a, Self::Summary> + Ord;
fn key(&self) -> Self::Key;
}
/// A type that describes the Sum of all [`Item`]s in a subtree of the [`SumTree`]
///
/// Each Summary type can have multiple [`Dimension`]s that it measures,
/// which can be used to navigate the tree
pub trait Summary: Clone {
type Context<'a>: Copy;
fn zero<'a>(cx: Self::Context<'a>) -> Self;
fn add_summary<'a>(&mut self, summary: &Self, cx: Self::Context<'a>);
}
pub trait ContextLessSummary: Clone {
fn zero() -> Self;
fn add_summary(&mut self, summary: &Self);
}
impl<T: ContextLessSummary> Summary for T {
type Context<'a> = ();
fn zero<'a>((): ()) -> Self {
T::zero()
}
fn add_summary<'a>(&mut self, summary: &Self, (): ()) {
T::add_summary(self, summary)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NoSummary;
/// Catch-all implementation for when you need something that implements [`Summary`] without a specific type.
/// We implement it on a `NoSummary` instead of re-using `()`, as that avoids blanket impl collisions with `impl<T: Summary> Dimension for T`
/// (as we also need unit type to be a fill-in dimension)
impl ContextLessSummary for NoSummary {
fn zero() -> Self {
NoSummary
}
fn add_summary(&mut self, _: &Self) {}
}
/// Each [`Summary`] type can have more than one [`Dimension`] type that it measures.
///
/// You can use dimensions to seek to a specific location in the [`SumTree`]
///
/// # Example:
/// Zed's rope has a `TextSummary` type that summarizes lines, characters, and bytes.
/// Each of these are different dimensions we may want to seek to
pub trait Dimension<'a, S: Summary>: Clone {
fn zero(cx: S::Context<'_>) -> Self;
fn add_summary(&mut self, summary: &'a S, cx: S::Context<'_>);
fn from_summary(summary: &'a S, cx: S::Context<'_>) -> Self {
let mut dimension = Self::zero(cx);
dimension.add_summary(summary, cx);
dimension
}
}
impl<'a, T: Summary> Dimension<'a, T> for T {
fn zero(cx: T::Context<'_>) -> Self {
Summary::zero(cx)
}
fn add_summary(&mut self, summary: &'a T, cx: T::Context<'_>) {
Summary::add_summary(self, summary, cx);
}
}
pub trait SeekTarget<'a, S: Summary, D: Dimension<'a, S>> {
fn cmp(&self, cursor_location: &D, cx: S::Context<'_>) -> Ordering;
}
impl<'a, S: Summary, D: Dimension<'a, S> + Ord> SeekTarget<'a, S, D> for D {
fn cmp(&self, cursor_location: &Self, _: S::Context<'_>) -> Ordering {
Ord::cmp(self, cursor_location)
}
}
impl<'a, T: Summary> Dimension<'a, T> for () {
fn zero(_: T::Context<'_>) -> Self {}
fn add_summary(&mut self, _: &'a T, _: T::Context<'_>) {}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Dimensions<D1, D2, D3 = ()>(pub D1, pub D2, pub D3);
impl<'a, T: Summary, D1: Dimension<'a, T>, D2: Dimension<'a, T>, D3: Dimension<'a, T>>
Dimension<'a, T> for Dimensions<D1, D2, D3>
{
fn zero(cx: T::Context<'_>) -> Self {
Dimensions(D1::zero(cx), D2::zero(cx), D3::zero(cx))
}
fn add_summary(&mut self, summary: &'a T, cx: T::Context<'_>) {
self.0.add_summary(summary, cx);
self.1.add_summary(summary, cx);
self.2.add_summary(summary, cx);
}
}
impl<'a, S, D1, D2, D3> SeekTarget<'a, S, Dimensions<D1, D2, D3>> for D1
where
S: Summary,
D1: SeekTarget<'a, S, D1> + Dimension<'a, S>,
D2: Dimension<'a, S>,
D3: Dimension<'a, S>,
{
fn cmp(&self, cursor_location: &Dimensions<D1, D2, D3>, cx: S::Context<'_>) -> Ordering {
self.cmp(&cursor_location.0, cx)
}
}
/// Bias is used to settle ambiguities when determining positions in an ordered sequence.
///
/// The primary use case is for text, where Bias influences
/// which character an offset or anchor is associated with.
///
/// # Examples
/// Given the buffer `AˇBCD`:
/// - The offset of the cursor is 1
/// - [Bias::Left] would attach the cursor to the character `A`
/// - [Bias::Right] would attach the cursor to the character `B`
///
/// Given the buffer `A«BCˇ»D`:
/// - The offset of the cursor is 3, and the selection is from 1 to 3
/// - The left anchor of the selection has [Bias::Right], attaching it to the character `B`
/// - The right anchor of the selection has [Bias::Left], attaching it to the character `C`
///
/// Given the buffer `{ˇ<...>`, where `<...>` is a folded region:
/// - The display offset of the cursor is 1, but the offset in the buffer is determined by the bias
/// - [Bias::Left] would attach the cursor to the character `{`, with a buffer offset of 1
/// - [Bias::Right] would attach the cursor to the first character of the folded region,
/// and the buffer offset would be the offset of the first character of the folded region
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash, Default)]
pub enum Bias {
/// Attach to the character on the left
#[default]
Left,
/// Attach to the character on the right
Right,
}
impl Bias {
pub fn invert(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
}
/// A B+ tree in which each leaf node contains `Item`s of type `T` and a `Summary`s for each `Item`.
/// Each internal node contains a `Summary` of the items in its subtree.
///
/// The maximum number of items per node is `TREE_BASE * 2`.
///
/// Any [`Dimension`] supported by the [`Summary`] type can be used to seek to a specific location in the tree.
#[derive(Clone)]
pub struct SumTree<T: Item>(Arc<Node<T>>);
impl<T> fmt::Debug for SumTree<T>
where
T: fmt::Debug + Item,
T::Summary: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("SumTree").field(&self.0).finish()
}
}
impl<T: Item> SumTree<T> {
pub fn new(cx: <T::Summary as Summary>::Context<'_>) -> Self {
SumTree(Arc::new(Node::Leaf {
summary: <T::Summary as Summary>::zero(cx),
items: ArrayVec::new(),
item_summaries: ArrayVec::new(),
}))
}
/// Useful in cases where the item type has a non-trivial context type, but the zero value of the summary type doesn't depend on that context.
pub fn from_summary(summary: T::Summary) -> Self {
SumTree(Arc::new(Node::Leaf {
summary,
items: ArrayVec::new(),
item_summaries: ArrayVec::new(),
}))
}
pub fn from_item(item: T, cx: <T::Summary as Summary>::Context<'_>) -> Self {
let mut tree = Self::new(cx);
tree.push(item, cx);
tree
}
pub fn from_iter<I: IntoIterator<Item = T>>(
iter: I,
cx: <T::Summary as Summary>::Context<'_>,
) -> Self {
let mut nodes = Vec::new();
let mut iter = iter.into_iter().fuse().peekable();
while iter.peek().is_some() {
let items: ArrayVec<T, { 2 * TREE_BASE }> = iter.by_ref().take(2 * TREE_BASE).collect();
let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
items.iter().map(|item| item.summary(cx)).collect();
let mut summary = item_summaries[0].clone();
for item_summary in &item_summaries[1..] {
<T::Summary as Summary>::add_summary(&mut summary, item_summary, cx);
}
nodes.push(Node::Leaf {
summary,
items,
item_summaries,
});
}
let mut parent_nodes = Vec::new();
let mut height = 0;
while nodes.len() > 1 {
height += 1;
let mut current_parent_node = None;
for child_node in nodes.drain(..) {
let parent_node = current_parent_node.get_or_insert_with(|| Node::Internal {
summary: <T::Summary as Summary>::zero(cx),
height,
child_summaries: ArrayVec::new(),
child_trees: ArrayVec::new(),
});
let Node::Internal {
summary,
child_summaries,
child_trees,
..
} = parent_node
else {
unreachable!()
};
let child_summary = child_node.summary();
<T::Summary as Summary>::add_summary(summary, child_summary, cx);
child_summaries.push(child_summary.clone());
child_trees.push(Self(Arc::new(child_node)));
if child_trees.len() == 2 * TREE_BASE {
parent_nodes.extend(current_parent_node.take());
}
}
parent_nodes.extend(current_parent_node.take());
mem::swap(&mut nodes, &mut parent_nodes);
}
if nodes.is_empty() {
Self::new(cx)
} else {
debug_assert_eq!(nodes.len(), 1);
Self(Arc::new(nodes.pop().unwrap()))
}
}
pub fn from_par_iter<I, Iter>(iter: I, cx: <T::Summary as Summary>::Context<'_>) -> Self
where
I: IntoParallelIterator<Iter = Iter>,
Iter: IndexedParallelIterator<Item = T>,
T: Send + Sync,
T::Summary: Send + Sync,
for<'a> <T::Summary as Summary>::Context<'a>: Sync,
{
let mut nodes = iter
.into_par_iter()
.chunks(2 * TREE_BASE)
.map(|items| {
let items: ArrayVec<T, { 2 * TREE_BASE }> = items.into_iter().collect();
let item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> =
items.iter().map(|item| item.summary(cx)).collect();
let mut summary = item_summaries[0].clone();
for item_summary in &item_summaries[1..] {
<T::Summary as Summary>::add_summary(&mut summary, item_summary, cx);
}
SumTree(Arc::new(Node::Leaf {
summary,
items,
item_summaries,
}))
})
.collect::<Vec<_>>();
let mut height = 0;
while nodes.len() > 1 {
height += 1;
nodes = nodes
.into_par_iter()
.chunks(2 * TREE_BASE)
.map(|child_nodes| {
let child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }> =
child_nodes.into_iter().collect();
let child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }> = child_trees
.iter()
.map(|child_tree| child_tree.summary().clone())
.collect();
let mut summary = child_summaries[0].clone();
for child_summary in &child_summaries[1..] {
<T::Summary as Summary>::add_summary(&mut summary, child_summary, cx);
}
SumTree(Arc::new(Node::Internal {
height,
summary,
child_summaries,
child_trees,
}))
})
.collect::<Vec<_>>();
}
if nodes.is_empty() {
Self::new(cx)
} else {
debug_assert_eq!(nodes.len(), 1);
nodes.pop().unwrap()
}
}
#[allow(unused)]
pub fn items<'a>(&'a self, cx: <T::Summary as Summary>::Context<'a>) -> Vec<T> {
let mut items = Vec::new();
let mut cursor = self.cursor::<()>(cx);
cursor.next();
while let Some(item) = cursor.item() {
items.push(item.clone());
cursor.next();
}
items
}
pub fn iter(&self) -> Iter<'_, T> {
Iter::new(self)
}
pub fn cursor<'a, 'b, S>(
&'a self,
cx: <T::Summary as Summary>::Context<'b>,
) -> Cursor<'a, 'b, T, S>
where
S: Dimension<'a, T::Summary>,
{
Cursor::new(self, cx)
}
/// Note: If the summary type requires a non `()` context, then the filter cursor
/// that is returned cannot be used with Rust's iterators.
pub fn filter<'a, 'b, F, U>(
&'a self,
cx: <T::Summary as Summary>::Context<'b>,
filter_node: F,
) -> FilterCursor<'a, 'b, F, T, U>
where
F: FnMut(&T::Summary) -> bool,
U: Dimension<'a, T::Summary>,
{
FilterCursor::new(self, cx, filter_node)
}
#[allow(dead_code)]
pub fn first(&self) -> Option<&T> {
self.leftmost_leaf().0.items().first()
}
pub fn last(&self) -> Option<&T> {
self.rightmost_leaf().0.items().last()
}
pub fn update_last(
&mut self,
f: impl FnOnce(&mut T),
cx: <T::Summary as Summary>::Context<'_>,
) {
self.update_last_recursive(f, cx);
}
fn update_last_recursive(
&mut self,
f: impl FnOnce(&mut T),
cx: <T::Summary as Summary>::Context<'_>,
) -> Option<T::Summary> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
summary,
child_summaries,
child_trees,
..
} => {
let last_summary = child_summaries.last_mut().unwrap();
let last_child = child_trees.last_mut().unwrap();
*last_summary = last_child.update_last_recursive(f, cx).unwrap();
*summary = sum(child_summaries.iter(), cx);
Some(summary.clone())
}
Node::Leaf {
summary,
items,
item_summaries,
} => {
if let Some((item, item_summary)) = items.last_mut().zip(item_summaries.last_mut())
{
(f)(item);
*item_summary = item.summary(cx);
*summary = sum(item_summaries.iter(), cx);
Some(summary.clone())
} else {
None
}
}
}
}
pub fn extent<'a, D: Dimension<'a, T::Summary>>(
&'a self,
cx: <T::Summary as Summary>::Context<'_>,
) -> D {
let mut extent = D::zero(cx);
match self.0.as_ref() {
Node::Internal { summary, .. } | Node::Leaf { summary, .. } => {
extent.add_summary(summary, cx);
}
}
extent
}
pub fn summary(&self) -> &T::Summary {
match self.0.as_ref() {
Node::Internal { summary, .. } => summary,
Node::Leaf { summary, .. } => summary,
}
}
pub fn is_empty(&self) -> bool {
match self.0.as_ref() {
Node::Internal { .. } => false,
Node::Leaf { items, .. } => items.is_empty(),
}
}
pub fn extend<I>(&mut self, iter: I, cx: <T::Summary as Summary>::Context<'_>)
where
I: IntoIterator<Item = T>,
{
self.append(Self::from_iter(iter, cx), cx);
}
pub fn par_extend<I, Iter>(&mut self, iter: I, cx: <T::Summary as Summary>::Context<'_>)
where
I: IntoParallelIterator<Iter = Iter>,
Iter: IndexedParallelIterator<Item = T>,
T: Send + Sync,
T::Summary: Send + Sync,
for<'a> <T::Summary as Summary>::Context<'a>: Sync,
{
self.append(Self::from_par_iter(iter, cx), cx);
}
pub fn push(&mut self, item: T, cx: <T::Summary as Summary>::Context<'_>) {
let summary = item.summary(cx);
self.append(
SumTree(Arc::new(Node::Leaf {
summary: summary.clone(),
items: ArrayVec::from_iter(Some(item)),
item_summaries: ArrayVec::from_iter(Some(summary)),
})),
cx,
);
}
pub fn append(&mut self, other: Self, cx: <T::Summary as Summary>::Context<'_>) {
if self.is_empty() {
*self = other;
} else if !other.0.is_leaf() || !other.0.items().is_empty() {
if self.0.height() < other.0.height() {
for tree in other.0.child_trees() {
self.append(tree.clone(), cx);
}
} else if let Some(split_tree) = self.push_tree_recursive(other, cx) {
*self = Self::from_child_trees(self.clone(), split_tree, cx);
}
}
}
fn push_tree_recursive(
&mut self,
other: SumTree<T>,
cx: <T::Summary as Summary>::Context<'_>,
) -> Option<SumTree<T>> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
height,
summary,
child_summaries,
child_trees,
..
} => {
let other_node = other.0.clone();
<T::Summary as Summary>::add_summary(summary, other_node.summary(), cx);
let height_delta = *height - other_node.height();
let mut summaries_to_append = ArrayVec::<T::Summary, { 2 * TREE_BASE }>::new();
let mut trees_to_append = ArrayVec::<SumTree<T>, { 2 * TREE_BASE }>::new();
if height_delta == 0 {
summaries_to_append.extend(other_node.child_summaries().iter().cloned());
trees_to_append.extend(other_node.child_trees().iter().cloned());
} else if height_delta == 1 && !other_node.is_underflowing() {
summaries_to_append.push(other_node.summary().clone());
trees_to_append.push(other)
} else {
let tree_to_append = child_trees
.last_mut()
.unwrap()
.push_tree_recursive(other, cx);
*child_summaries.last_mut().unwrap() =
child_trees.last().unwrap().0.summary().clone();
if let Some(split_tree) = tree_to_append {
summaries_to_append.push(split_tree.0.summary().clone());
trees_to_append.push(split_tree);
}
}
let child_count = child_trees.len() + trees_to_append.len();
if child_count > 2 * TREE_BASE {
let left_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
let right_summaries: ArrayVec<_, { 2 * TREE_BASE }>;
let left_trees;
let right_trees;
let midpoint = (child_count + child_count % 2) / 2;
{
let mut all_summaries = child_summaries
.iter()
.chain(summaries_to_append.iter())
.cloned();
left_summaries = all_summaries.by_ref().take(midpoint).collect();
right_summaries = all_summaries.collect();
let mut all_trees =
child_trees.iter().chain(trees_to_append.iter()).cloned();
left_trees = all_trees.by_ref().take(midpoint).collect();
right_trees = all_trees.collect();
}
*summary = sum(left_summaries.iter(), cx);
*child_summaries = left_summaries;
*child_trees = left_trees;
Some(SumTree(Arc::new(Node::Internal {
height: *height,
summary: sum(right_summaries.iter(), cx),
child_summaries: right_summaries,
child_trees: right_trees,
})))
} else {
child_summaries.extend(summaries_to_append);
child_trees.extend(trees_to_append);
None
}
}
Node::Leaf {
summary,
items,
item_summaries,
} => {
let other_node = other.0;
let child_count = items.len() + other_node.items().len();
if child_count > 2 * TREE_BASE {
let left_items;
let right_items;
let left_summaries;
let right_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>;
let midpoint = (child_count + child_count % 2) / 2;
{
let mut all_items = items.iter().chain(other_node.items().iter()).cloned();
left_items = all_items.by_ref().take(midpoint).collect();
right_items = all_items.collect();
let mut all_summaries = item_summaries
.iter()
.chain(other_node.child_summaries())
.cloned();
left_summaries = all_summaries.by_ref().take(midpoint).collect();
right_summaries = all_summaries.collect();
}
*items = left_items;
*item_summaries = left_summaries;
*summary = sum(item_summaries.iter(), cx);
Some(SumTree(Arc::new(Node::Leaf {
items: right_items,
summary: sum(right_summaries.iter(), cx),
item_summaries: right_summaries,
})))
} else {
<T::Summary as Summary>::add_summary(summary, other_node.summary(), cx);
items.extend(other_node.items().iter().cloned());
item_summaries.extend(other_node.child_summaries().iter().cloned());
None
}
}
}
}
fn from_child_trees(
left: SumTree<T>,
right: SumTree<T>,
cx: <T::Summary as Summary>::Context<'_>,
) -> Self {
let height = left.0.height() + 1;
let mut child_summaries = ArrayVec::new();
child_summaries.push(left.0.summary().clone());
child_summaries.push(right.0.summary().clone());
let mut child_trees = ArrayVec::new();
child_trees.push(left);
child_trees.push(right);
SumTree(Arc::new(Node::Internal {
height,
summary: sum(child_summaries.iter(), cx),
child_summaries,
child_trees,
}))
}
fn leftmost_leaf(&self) -> &Self {
match *self.0 {
Node::Leaf { .. } => self,
Node::Internal {
ref child_trees, ..
} => child_trees.first().unwrap().leftmost_leaf(),
}
}
fn rightmost_leaf(&self) -> &Self {
match *self.0 {
Node::Leaf { .. } => self,
Node::Internal {
ref child_trees, ..
} => child_trees.last().unwrap().rightmost_leaf(),
}
}
}
impl<T: Item + PartialEq> PartialEq for SumTree<T> {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
impl<T: Item + Eq> Eq for SumTree<T> {}
impl<T: KeyedItem> SumTree<T> {
pub fn insert_or_replace<'a, 'b>(
&'a mut self,
item: T,
cx: <T::Summary as Summary>::Context<'b>,
) -> Option<T> {
let mut replaced = None;
{
let mut cursor = self.cursor::<T::Key>(cx);
let mut new_tree = cursor.slice(&item.key(), Bias::Left);
if let Some(cursor_item) = cursor.item()
&& cursor_item.key() == item.key()
{
replaced = Some(cursor_item.clone());
cursor.next();
}
new_tree.push(item, cx);
new_tree.append(cursor.suffix(), cx);
drop(cursor);
*self = new_tree
};
replaced
}
pub fn remove(&mut self, key: &T::Key, cx: <T::Summary as Summary>::Context<'_>) -> Option<T> {
let mut removed = None;
*self = {
let mut cursor = self.cursor::<T::Key>(cx);
let mut new_tree = cursor.slice(key, Bias::Left);
if let Some(item) = cursor.item()
&& item.key() == *key
{
removed = Some(item.clone());
cursor.next();
}
new_tree.append(cursor.suffix(), cx);
new_tree
};
removed
}
pub fn edit(
&mut self,
mut edits: Vec<Edit<T>>,
cx: <T::Summary as Summary>::Context<'_>,
) -> Vec<T> {
if edits.is_empty() {
return Vec::new();
}
let mut removed = Vec::new();
edits.sort_unstable_by_key(|item| item.key());
*self = {
let mut cursor = self.cursor::<T::Key>(cx);
let mut new_tree = SumTree::new(cx);
let mut buffered_items = Vec::new();
cursor.seek(&T::Key::zero(cx), Bias::Left);
for edit in edits {
let new_key = edit.key();
let mut old_item = cursor.item();
if old_item
.as_ref()
.is_some_and(|old_item| old_item.key() < new_key)
{
new_tree.extend(buffered_items.drain(..), cx);
let slice = cursor.slice(&new_key, Bias::Left);
new_tree.append(slice, cx);
old_item = cursor.item();
}
if let Some(old_item) = old_item
&& old_item.key() == new_key
{
removed.push(old_item.clone());
cursor.next();
}
match edit {
Edit::Insert(item) => {
buffered_items.push(item);
}
Edit::Remove(_) => {}
}
}
new_tree.extend(buffered_items, cx);
new_tree.append(cursor.suffix(), cx);
new_tree
};
removed
}
pub fn get<'a>(
&'a self,
key: &T::Key,
cx: <T::Summary as Summary>::Context<'a>,
) -> Option<&'a T> {
let mut cursor = self.cursor::<T::Key>(cx);
if cursor.seek(key, Bias::Left) {
cursor.item()
} else {
None
}
}
}
impl<T, S> Default for SumTree<T>
where
T: Item<Summary = S>,
S: for<'a> Summary<Context<'a> = ()>,
{
fn default() -> Self {
Self::new(())
}
}
#[derive(Clone)]
pub enum Node<T: Item> {
Internal {
height: u8,
summary: T::Summary,
child_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
child_trees: ArrayVec<SumTree<T>, { 2 * TREE_BASE }>,
},
Leaf {
summary: T::Summary,
items: ArrayVec<T, { 2 * TREE_BASE }>,
item_summaries: ArrayVec<T::Summary, { 2 * TREE_BASE }>,
},
}
impl<T> fmt::Debug for Node<T>
where
T: Item + fmt::Debug,
T::Summary: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Node::Internal {
height,
summary,
child_summaries,
child_trees,
} => f
.debug_struct("Internal")
.field("height", height)
.field("summary", summary)
.field("child_summaries", child_summaries)
.field("child_trees", child_trees)
.finish(),
Node::Leaf {
summary,
items,
item_summaries,
} => f
.debug_struct("Leaf")
.field("summary", summary)
.field("items", items)
.field("item_summaries", item_summaries)
.finish(),
}
}
}
impl<T: Item> Node<T> {
fn is_leaf(&self) -> bool {
matches!(self, Node::Leaf { .. })
}
fn height(&self) -> u8 {
match self {
Node::Internal { height, .. } => *height,
Node::Leaf { .. } => 0,
}
}
fn summary(&self) -> &T::Summary {
match self {
Node::Internal { summary, .. } => summary,
Node::Leaf { summary, .. } => summary,
}
}
fn child_summaries(&self) -> &[T::Summary] {
match self {
Node::Internal {
child_summaries, ..
} => child_summaries.as_slice(),
Node::Leaf { item_summaries, .. } => item_summaries.as_slice(),
}
}
fn child_trees(&self) -> &ArrayVec<SumTree<T>, { 2 * TREE_BASE }> {
match self {
Node::Internal { child_trees, .. } => child_trees,
Node::Leaf { .. } => panic!("Leaf nodes have no child trees"),
}
}
fn items(&self) -> &ArrayVec<T, { 2 * TREE_BASE }> {
match self {
Node::Leaf { items, .. } => items,
Node::Internal { .. } => panic!("Internal nodes have no items"),
}
}
fn is_underflowing(&self) -> bool {
match self {
Node::Internal { child_trees, .. } => child_trees.len() < TREE_BASE,
Node::Leaf { items, .. } => items.len() < TREE_BASE,
}
}
}
#[derive(Debug)]
pub enum Edit<T: KeyedItem> {
Insert(T),
Remove(T::Key),
}
impl<T: KeyedItem> Edit<T> {
fn key(&self) -> T::Key {
match self {
Edit::Insert(item) => item.key(),
Edit::Remove(key) => key.clone(),
}
}
}
fn sum<'a, T, I>(iter: I, cx: T::Context<'_>) -> T
where
T: 'a + Summary,
I: Iterator<Item = &'a T>,
{
let mut sum = T::zero(cx);
for value in iter {
sum.add_summary(value, cx);
}
sum
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{distr::StandardUniform, prelude::*};
use std::cmp;
#[ctor::ctor]
fn init_logger() {
zlog::init_test();
}
#[test]
fn test_extend_and_push_tree() {
let mut tree1 = SumTree::default();
tree1.extend(0..20, ());
let mut tree2 = SumTree::default();
tree2.extend(50..100, ());
tree1.append(tree2, ());
assert_eq!(tree1.items(()), (0..20).chain(50..100).collect::<Vec<u8>>());
}
#[test]
fn test_random() {
let mut starting_seed = 0;
if let Ok(value) = std::env::var("SEED") {
starting_seed = value.parse().expect("invalid SEED variable");
}
let mut num_iterations = 100;
if let Ok(value) = std::env::var("ITERATIONS") {
num_iterations = value.parse().expect("invalid ITERATIONS variable");
}
let num_operations = std::env::var("OPERATIONS")
.map_or(5, |o| o.parse().expect("invalid OPERATIONS variable"));
for seed in starting_seed..(starting_seed + num_iterations) {
eprintln!("seed = {}", seed);
let mut rng = StdRng::seed_from_u64(seed);
let rng = &mut rng;
let mut tree = SumTree::<u8>::default();
let count = rng.random_range(0..10);
if rng.random() {
tree.extend(rng.sample_iter(StandardUniform).take(count), ());
} else {
let items = rng
.sample_iter(StandardUniform)
.take(count)
.collect::<Vec<_>>();
tree.par_extend(items, ());
}
for _ in 0..num_operations {
let splice_end = rng.random_range(0..tree.extent::<Count>(()).0 + 1);
let splice_start = rng.random_range(0..splice_end + 1);
let count = rng.random_range(0..10);
let tree_end = tree.extent::<Count>(());
let new_items = rng
.sample_iter(StandardUniform)
.take(count)
.collect::<Vec<u8>>();
let mut reference_items = tree.items(());
reference_items.splice(splice_start..splice_end, new_items.clone());
tree = {
let mut cursor = tree.cursor::<Count>(());
let mut new_tree = cursor.slice(&Count(splice_start), Bias::Right);
if rng.random() {
new_tree.extend(new_items, ());
} else {
new_tree.par_extend(new_items, ());
}
cursor.seek(&Count(splice_end), Bias::Right);
new_tree.append(cursor.slice(&tree_end, Bias::Right), ());
new_tree
};
assert_eq!(tree.items(()), reference_items);
assert_eq!(
tree.iter().collect::<Vec<_>>(),
tree.cursor::<()>(()).collect::<Vec<_>>()
);
log::info!("tree items: {:?}", tree.items(()));
let mut filter_cursor =
tree.filter::<_, Count>((), |summary| summary.contains_even);
let expected_filtered_items = tree
.items(())
.into_iter()
.enumerate()
.filter(|(_, item)| (item & 1) == 0)
.collect::<Vec<_>>();
let mut item_ix = if rng.random() {
filter_cursor.next();
0
} else {
filter_cursor.prev();
expected_filtered_items.len().saturating_sub(1)
};
while item_ix < expected_filtered_items.len() {
log::info!("filter_cursor, item_ix: {}", item_ix);
let actual_item = filter_cursor.item().unwrap();
let (reference_index, reference_item) = expected_filtered_items[item_ix];
assert_eq!(actual_item, &reference_item);
assert_eq!(filter_cursor.start().0, reference_index);
log::info!("next");
filter_cursor.next();
item_ix += 1;
while item_ix > 0 && rng.random_bool(0.2) {
log::info!("prev");
filter_cursor.prev();
item_ix -= 1;
if item_ix == 0 && rng.random_bool(0.2) {
filter_cursor.prev();
assert_eq!(filter_cursor.item(), None);
assert_eq!(filter_cursor.start().0, 0);
filter_cursor.next();
}
}
}
assert_eq!(filter_cursor.item(), None);
let mut before_start = false;
let mut cursor = tree.cursor::<Count>(());
let start_pos = rng.random_range(0..=reference_items.len());
cursor.seek(&Count(start_pos), Bias::Right);
let mut pos = rng.random_range(start_pos..=reference_items.len());
cursor.seek_forward(&Count(pos), Bias::Right);
for i in 0..10 {
assert_eq!(cursor.start().0, pos);
if pos > 0 {
assert_eq!(cursor.prev_item().unwrap(), &reference_items[pos - 1]);
} else {
assert_eq!(cursor.prev_item(), None);
}
if pos < reference_items.len() && !before_start {
assert_eq!(cursor.item().unwrap(), &reference_items[pos]);
} else {
assert_eq!(cursor.item(), None);
}
if before_start {
assert_eq!(cursor.next_item(), reference_items.first());
} else if pos + 1 < reference_items.len() {
assert_eq!(cursor.next_item().unwrap(), &reference_items[pos + 1]);
} else {
assert_eq!(cursor.next_item(), None);
}
if i < 5 {
cursor.next();
if pos < reference_items.len() {
pos += 1;
before_start = false;
}
} else {
cursor.prev();
if pos == 0 {
before_start = true;
}
pos = pos.saturating_sub(1);
}
}
}
for _ in 0..10 {
let end = rng.random_range(0..tree.extent::<Count>(()).0 + 1);
let start = rng.random_range(0..end + 1);
let start_bias = if rng.random() {
Bias::Left
} else {
Bias::Right
};
let end_bias = if rng.random() {
Bias::Left
} else {
Bias::Right
};
let mut cursor = tree.cursor::<Count>(());
cursor.seek(&Count(start), start_bias);
let slice = cursor.slice(&Count(end), end_bias);
cursor.seek(&Count(start), start_bias);
let summary = cursor.summary::<_, Sum>(&Count(end), end_bias);
assert_eq!(summary.0, slice.summary().sum);
}
}
}
#[test]
fn test_cursor() {
// Empty tree
let tree = SumTree::<u8>::default();
let mut cursor = tree.cursor::<IntegersSummary>(());
assert_eq!(
cursor.slice(&Count(0), Bias::Right).items(()),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 0);
cursor.prev();
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 0);
cursor.next();
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 0);
// Single-element tree
let mut tree = SumTree::<u8>::default();
tree.extend(vec![1], ());
let mut cursor = tree.cursor::<IntegersSummary>(());
assert_eq!(
cursor.slice(&Count(0), Bias::Right).items(()),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 0);
cursor.next();
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 1);
cursor.prev();
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 0);
let mut cursor = tree.cursor::<IntegersSummary>(());
assert_eq!(cursor.slice(&Count(1), Bias::Right).items(()), [1]);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 1);
cursor.seek(&Count(0), Bias::Right);
assert_eq!(
cursor
.slice(&tree.extent::<Count>(()), Bias::Right)
.items(()),
[1]
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 1);
// Multiple-element tree
let mut tree = SumTree::default();
tree.extend(vec![1, 2, 3, 4, 5, 6], ());
let mut cursor = tree.cursor::<IntegersSummary>(());
assert_eq!(cursor.slice(&Count(2), Bias::Right).items(()), [1, 2]);
assert_eq!(cursor.item(), Some(&3));
assert_eq!(cursor.prev_item(), Some(&2));
assert_eq!(cursor.next_item(), Some(&4));
assert_eq!(cursor.start().sum, 3);
cursor.next();
assert_eq!(cursor.item(), Some(&4));
assert_eq!(cursor.prev_item(), Some(&3));
assert_eq!(cursor.next_item(), Some(&5));
assert_eq!(cursor.start().sum, 6);
cursor.next();
assert_eq!(cursor.item(), Some(&5));
assert_eq!(cursor.prev_item(), Some(&4));
assert_eq!(cursor.next_item(), Some(&6));
assert_eq!(cursor.start().sum, 10);
cursor.next();
assert_eq!(cursor.item(), Some(&6));
assert_eq!(cursor.prev_item(), Some(&5));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 15);
cursor.next();
cursor.next();
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 21);
cursor.prev();
assert_eq!(cursor.item(), Some(&6));
assert_eq!(cursor.prev_item(), Some(&5));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 15);
cursor.prev();
assert_eq!(cursor.item(), Some(&5));
assert_eq!(cursor.prev_item(), Some(&4));
assert_eq!(cursor.next_item(), Some(&6));
assert_eq!(cursor.start().sum, 10);
cursor.prev();
assert_eq!(cursor.item(), Some(&4));
assert_eq!(cursor.prev_item(), Some(&3));
assert_eq!(cursor.next_item(), Some(&5));
assert_eq!(cursor.start().sum, 6);
cursor.prev();
assert_eq!(cursor.item(), Some(&3));
assert_eq!(cursor.prev_item(), Some(&2));
assert_eq!(cursor.next_item(), Some(&4));
assert_eq!(cursor.start().sum, 3);
cursor.prev();
assert_eq!(cursor.item(), Some(&2));
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.next_item(), Some(&3));
assert_eq!(cursor.start().sum, 1);
cursor.prev();
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), Some(&2));
assert_eq!(cursor.start().sum, 0);
cursor.prev();
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), Some(&1));
assert_eq!(cursor.start().sum, 0);
cursor.next();
assert_eq!(cursor.item(), Some(&1));
assert_eq!(cursor.prev_item(), None);
assert_eq!(cursor.next_item(), Some(&2));
assert_eq!(cursor.start().sum, 0);
let mut cursor = tree.cursor::<IntegersSummary>(());
assert_eq!(
cursor
.slice(&tree.extent::<Count>(()), Bias::Right)
.items(()),
tree.items(())
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 21);
cursor.seek(&Count(3), Bias::Right);
assert_eq!(
cursor
.slice(&tree.extent::<Count>(()), Bias::Right)
.items(()),
[4, 5, 6]
);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.next_item(), None);
assert_eq!(cursor.start().sum, 21);
// Seeking can bias left or right
cursor.seek(&Count(1), Bias::Left);
assert_eq!(cursor.item(), Some(&1));
cursor.seek(&Count(1), Bias::Right);
assert_eq!(cursor.item(), Some(&2));
// Slicing without resetting starts from where the cursor is parked at.
cursor.seek(&Count(1), Bias::Right);
assert_eq!(cursor.slice(&Count(3), Bias::Right).items(()), vec![2, 3]);
assert_eq!(cursor.slice(&Count(6), Bias::Left).items(()), vec![4, 5]);
assert_eq!(cursor.slice(&Count(6), Bias::Right).items(()), vec![6]);
}
#[test]
fn test_edit() {
let mut tree = SumTree::<u8>::default();
let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)], ());
assert_eq!(tree.items(()), vec![0, 1, 2]);
assert_eq!(removed, Vec::<u8>::new());
assert_eq!(tree.get(&0, ()), Some(&0));
assert_eq!(tree.get(&1, ()), Some(&1));
assert_eq!(tree.get(&2, ()), Some(&2));
assert_eq!(tree.get(&4, ()), None);
let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)], ());
assert_eq!(tree.items(()), vec![1, 2, 4]);
assert_eq!(removed, vec![0, 2]);
assert_eq!(tree.get(&0, ()), None);
assert_eq!(tree.get(&1, ()), Some(&1));
assert_eq!(tree.get(&2, ()), Some(&2));
assert_eq!(tree.get(&4, ()), Some(&4));
}
#[test]
fn test_from_iter() {
assert_eq!(
SumTree::from_iter(0..100, ()).items(()),
(0..100).collect::<Vec<_>>()
);
// Ensure `from_iter` works correctly when the given iterator restarts
// after calling `next` if `None` was already returned.
let mut ix = 0;
let iterator = std::iter::from_fn(|| {
ix = (ix + 1) % 2;
if ix == 1 { Some(1) } else { None }
});
assert_eq!(SumTree::from_iter(iterator, ()).items(()), vec![1]);
}
#[derive(Clone, Default, Debug)]
pub struct IntegersSummary {
count: usize,
sum: usize,
contains_even: bool,
max: u8,
}
#[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)]
struct Count(usize);
#[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)]
struct Sum(usize);
impl Item for u8 {
type Summary = IntegersSummary;
fn summary(&self, _cx: ()) -> Self::Summary {
IntegersSummary {
count: 1,
sum: *self as usize,
contains_even: (*self & 1) == 0,
max: *self,
}
}
}
impl KeyedItem for u8 {
type Key = u8;
fn key(&self) -> Self::Key {
*self
}
}
impl ContextLessSummary for IntegersSummary {
fn zero() -> Self {
Default::default()
}
fn add_summary(&mut self, other: &Self) {
self.count += other.count;
self.sum += other.sum;
self.contains_even |= other.contains_even;
self.max = cmp::max(self.max, other.max);
}
}
impl Dimension<'_, IntegersSummary> for u8 {
fn zero(_cx: ()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &IntegersSummary, _: ()) {
*self = summary.max;
}
}
impl Dimension<'_, IntegersSummary> for Count {
fn zero(_cx: ()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &IntegersSummary, _: ()) {
self.0 += summary.count;
}
}
impl SeekTarget<'_, IntegersSummary, IntegersSummary> for Count {
fn cmp(&self, cursor_location: &IntegersSummary, _: ()) -> Ordering {
self.0.cmp(&cursor_location.count)
}
}
impl Dimension<'_, IntegersSummary> for Sum {
fn zero(_cx: ()) -> Self {
Default::default()
}
fn add_summary(&mut self, summary: &IntegersSummary, _: ()) {
self.0 += summary.sum;
}
}
}