use spine::{ARITY_RANGE, Hasher, SkeletonStep, fold_frontier, frontier_for_size, nary_mr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShapeNode {
Leaf(u64),
Inner(Vec<ShapeNode>),
}
#[must_use]
pub fn build(size: u64, k: u64) -> Option<ShapeNode> {
if !ARITY_RANGE.contains(&k) || size == 0 {
return None;
}
let coords = frontier_for_size(size, k);
if coords.is_empty() {
return None;
}
let frontier: Vec<ShapeNode> = coords
.iter()
.map(|&(left, height)| perfect(left, height, k))
.collect();
Some(fold_frontier(frontier, k as usize, |chunk| {
ShapeNode::Inner(chunk.to_vec())
}))
}
pub(crate) fn perfect(left: u64, height: u32, k: u64) -> ShapeNode {
if height == 0 {
return ShapeNode::Leaf(left);
}
let child_span = k.pow(height - 1);
let children = (0..k)
.map(|c| perfect(left + c * child_span, height - 1, k))
.collect();
ShapeNode::Inner(children)
}
pub(crate) fn leftmost(node: &ShapeNode) -> u64 {
match node {
ShapeNode::Leaf(pos) => *pos,
ShapeNode::Inner(children) => leftmost(&children[0]),
}
}
pub(crate) fn rightmost(node: &ShapeNode) -> u64 {
match node {
ShapeNode::Leaf(pos) => *pos,
ShapeNode::Inner(children) => rightmost(children.last().expect("inner node has children")),
}
}
pub(crate) fn covers(node: &ShapeNode, index: u64) -> bool {
leftmost(node) <= index && index <= rightmost(node)
}
#[must_use]
pub fn rebalanced_skeleton(size: u64, k: u64, index: u64) -> Option<Vec<SkeletonStep>> {
if index >= size {
return None;
}
let shape = build(size, k)?;
let mut steps = Vec::new();
descend_skeleton(&shape, index, &mut steps);
steps.reverse();
Some(steps)
}
fn descend_skeleton(node: &ShapeNode, index: u64, out: &mut Vec<SkeletonStep>) {
if let ShapeNode::Inner(children) = node {
let position = children
.iter()
.position(|c| covers(c, index))
.expect("the path index is covered by exactly one child");
out.push(SkeletonStep {
position,
sibling_count: children.len() - 1,
});
descend_skeleton(&children[position], index, out);
}
}
#[must_use]
pub fn rebalanced_bag(hasher: &dyn Hasher, peaks: &[Vec<u8>], k: u64) -> Vec<u8> {
if peaks.is_empty() {
return hasher.empty();
}
fold_frontier(peaks.to_vec(), k as usize, |chunk| {
let refs: Vec<&[u8]> = chunk.iter().map(|v| v.as_slice()).collect();
nary_mr(hasher, &refs)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_undefined_shapes() {
assert_eq!(build(0, 2), None);
assert_eq!(build(4, 1), None);
assert_eq!(build(4, 257), None);
}
#[test]
fn singleton_is_a_lone_leaf() {
assert_eq!(build(1, 2), Some(ShapeNode::Leaf(0)));
}
#[test]
fn covers_every_position_in_order() {
for k in [2u64, 3, 5] {
for size in 1..=64u64 {
let mut seen = Vec::new();
collect_leaves(&build(size, k).expect("defined"), &mut seen);
let expected: Vec<u64> = (0..size).collect();
assert_eq!(seen, expected, "k={k} size={size}");
}
}
}
fn collect_leaves(node: &ShapeNode, out: &mut Vec<u64>) {
match node {
ShapeNode::Leaf(p) => out.push(*p),
ShapeNode::Inner(children) => {
for c in children {
collect_leaves(c, out);
}
},
}
}
}