973 Finger Tree
Tutorial
The Problem
Explore the finger tree — a purely functional deque with O(1) amortized push/pop at both ends and O(log n) split/concat. The classic recursive type creates an infinitely-nested FingerTree<Node<T>> that Rust's type system cannot express directly. This implementation uses a VecDeque-backed simplified finger tree to demonstrate the interface while explaining the structural challenge.
🎯 Learning Outcomes
FingerTree<Node<T>> is not a finite type in Rust without boxingVecDeque with consuming push/pop methodspush_front(self, x) -> Self returns a new treeVecDeque is sufficient vs when a true finger tree adds valueCode Example
//! 973: Finger Tree (Simplified)
//!
//! A persistent 2-3 finger tree providing a deque with O(1) amortized
//! `push_front` and `push_back` at both ends. This is a teaching-oriented
//! implementation that mirrors the classic 3-layer structure: two `Digit`
//! spines holding 1-4 elements each and a recursive inner tree of `Node`s.
//!
//! The spine is `Box<FingerTree<Node<T>>>`, so each level nests values one
//! type deeper than its parent; `Box` breaks what would otherwise be an
//! infinitely sized Rust type.
/// A digit holds 1 to 4 elements at one end of a `Deep` tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Digit<T> {
/// A single element.
One(T),
/// Two elements.
Two(T, T),
/// Three elements.
Three(T, T, T),
/// Four elements (maximum before overflow).
Four(T, T, T, T),
}
/// A node stored in the recursive spine; 2 or 3 elements.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node<T> {
/// A 2-element node.
Node2(T, T),
/// A 3-element node.
Node3(T, T, T),
}
/// A persistent 2-3 finger tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FingerTree<T> {
/// An empty tree.
Empty,
/// A tree with a single element.
Single(T),
/// A tree with a left digit, recursive spine, and right digit.
Deep(Digit<T>, Box<FingerTree<Node<T>>>, Digit<T>),
}
impl<T> Default for FingerTree<T> {
fn default() -> Self {
FingerTree::Empty
}
}
impl<T> FingerTree<T> {
/// Returns a new empty tree.
pub fn new() -> Self {
FingerTree::Empty
}
/// Returns `true` if the tree has no elements.
pub fn is_empty(&self) -> bool {
matches!(self, FingerTree::Empty)
}
/// Pushes `x` onto the front of the tree.
pub fn push_front(self, x: T) -> Self {
match self {
FingerTree::Empty => FingerTree::Single(x),
FingerTree::Single(y) => {
FingerTree::Deep(Digit::One(x), Box::new(FingerTree::Empty), Digit::One(y))
}
FingerTree::Deep(left, spine, right) => match left {
Digit::One(a) => FingerTree::Deep(Digit::Two(x, a), spine, right),
Digit::Two(a, b) => FingerTree::Deep(Digit::Three(x, a, b), spine, right),
Digit::Three(a, b, c) => FingerTree::Deep(Digit::Four(x, a, b, c), spine, right),
Digit::Four(a, b, c, d) => {
let overflow = Node::Node3(b, c, d);
let spine = Box::new(spine.push_front(overflow));
FingerTree::Deep(Digit::Two(x, a), spine, right)
}
},
}
}
/// Pushes `x` onto the back of the tree.
pub fn push_back(self, x: T) -> Self {
match self {
FingerTree::Empty => FingerTree::Single(x),
FingerTree::Single(y) => {
FingerTree::Deep(Digit::One(y), Box::new(FingerTree::Empty), Digit::One(x))
}
FingerTree::Deep(left, spine, right) => match right {
Digit::One(a) => FingerTree::Deep(left, spine, Digit::Two(a, x)),
Digit::Two(a, b) => FingerTree::Deep(left, spine, Digit::Three(a, b, x)),
Digit::Three(a, b, c) => FingerTree::Deep(left, spine, Digit::Four(a, b, c, x)),
Digit::Four(a, b, c, d) => {
let overflow = Node::Node3(a, b, c);
let spine = Box::new(spine.push_back(overflow));
FingerTree::Deep(left, spine, Digit::Two(d, x))
}
},
}
}
/// Flattens the tree into a `Vec` in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
FingerTree::Empty => Vec::new(),
FingerTree::Single(x) => vec![x],
FingerTree::Deep(left, spine, right) => {
let mut out = left.into_vec();
for node in spine.into_vec() {
out.extend(node.into_vec());
}
out.extend(right.into_vec());
out
}
}
}
}
impl<T> Digit<T> {
/// Consumes the digit and returns its elements in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
Digit::One(a) => vec![a],
Digit::Two(a, b) => vec![a, b],
Digit::Three(a, b, c) => vec![a, b, c],
Digit::Four(a, b, c, d) => vec![a, b, c, d],
}
}
}
impl<T> Node<T> {
/// Consumes the node and returns its elements in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
Node::Node2(a, b) => vec![a, b],
Node::Node3(a, b, c) => vec![a, b, c],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_tree_flattens_to_empty_vec() {
let t: FingerTree<i32> = FingerTree::new();
assert!(t.is_empty());
assert_eq!(t.into_vec(), Vec::<i32>::new());
}
#[test]
fn single_tree_has_one_element() {
let t = FingerTree::new().push_back(42);
assert_eq!(t, FingerTree::Single(42));
}
#[test]
fn mixed_pushes_preserve_order() {
let t = FingerTree::new()
.push_back(1)
.push_back(2)
.push_back(3)
.push_front(0)
.push_back(4)
.push_front(-1);
assert_eq!(t.into_vec(), vec![-1, 0, 1, 2, 3, 4]);
}
#[test]
fn push_back_many_preserves_order() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (1..=10).collect::<Vec<_>>());
}
#[test]
fn push_front_many_reverses_insertion() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_front(x));
assert_eq!(t.into_vec(), (1..=10).rev().collect::<Vec<_>>());
}
#[test]
fn long_sequence_forces_spine_growth() {
let n = 100;
let t = (0..n).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (0..n).collect::<Vec<_>>());
}
#[test]
fn interleaved_pushes_match_vecdeque() {
use std::collections::VecDeque;
let mut t = FingerTree::new();
let mut expected: VecDeque<i32> = VecDeque::new();
for i in 0..50 {
if i % 2 == 0 {
t = t.push_back(i);
expected.push_back(i);
} else {
t = t.push_front(i);
expected.push_front(i);
}
}
assert_eq!(t.into_vec(), Vec::from(expected));
}
#[test]
fn digit_into_vec_returns_elements_in_order() {
assert_eq!(Digit::One(1).into_vec(), vec![1]);
assert_eq!(Digit::Two(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Digit::Three(1, 2, 3).into_vec(), vec![1, 2, 3]);
assert_eq!(Digit::Four(1, 2, 3, 4).into_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn node_into_vec_returns_elements_in_order() {
assert_eq!(Node::Node2(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Node::Node3(1, 2, 3).into_vec(), vec![1, 2, 3]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Recursive type | Cannot express FingerTree<Node<T>> without boxing | Natural recursive type in Haskell/OCaml |
| Consuming API | fn push(self) -> Self | Persistent { t with ... } |
| Practical deque | VecDeque — O(1) amortized | Two-list or stdlib Queue |
| True finger tree | Requires Box<...> indirection | Data.Sequence (Haskell), BatDeque (OCaml) |
Finger trees generalize to sequences supporting any measured monoid (e.g., size, priority, index) with O(log n) split/concat. They are the theoretical foundation of functional sequence libraries.
OCaml Approach
(* Simplified finger tree as two-list deque *)
type 'a finger_tree = {
front: 'a list;
back: 'a list;
}
let empty = { front = []; back = [] }
let push_front x t = { t with front = x :: t.front }
let push_back x t = { t with back = x :: t.back }
let pop_front t = match t.front with
| [] -> (match List.rev t.back with
| [] -> None, t
| x :: rest -> Some x, { front = rest; back = [] })
| x :: rest -> Some x, { t with front = rest }
(* True finger tree: see Jane Street's Core.Deque or Batteries' Deque *)
OCaml's with record update syntax makes persistent push O(1). The two-list variant is the practical OCaml equivalent. For a true finger tree, Haskell's Data.Sequence or OCaml's BatDeque provides the full implementation.
Full Source
//! 973: Finger Tree (Simplified)
//!
//! A persistent 2-3 finger tree providing a deque with O(1) amortized
//! `push_front` and `push_back` at both ends. This is a teaching-oriented
//! implementation that mirrors the classic 3-layer structure: two `Digit`
//! spines holding 1-4 elements each and a recursive inner tree of `Node`s.
//!
//! The spine is `Box<FingerTree<Node<T>>>`, so each level nests values one
//! type deeper than its parent; `Box` breaks what would otherwise be an
//! infinitely sized Rust type.
/// A digit holds 1 to 4 elements at one end of a `Deep` tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Digit<T> {
/// A single element.
One(T),
/// Two elements.
Two(T, T),
/// Three elements.
Three(T, T, T),
/// Four elements (maximum before overflow).
Four(T, T, T, T),
}
/// A node stored in the recursive spine; 2 or 3 elements.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node<T> {
/// A 2-element node.
Node2(T, T),
/// A 3-element node.
Node3(T, T, T),
}
/// A persistent 2-3 finger tree.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FingerTree<T> {
/// An empty tree.
Empty,
/// A tree with a single element.
Single(T),
/// A tree with a left digit, recursive spine, and right digit.
Deep(Digit<T>, Box<FingerTree<Node<T>>>, Digit<T>),
}
impl<T> Default for FingerTree<T> {
fn default() -> Self {
FingerTree::Empty
}
}
impl<T> FingerTree<T> {
/// Returns a new empty tree.
pub fn new() -> Self {
FingerTree::Empty
}
/// Returns `true` if the tree has no elements.
pub fn is_empty(&self) -> bool {
matches!(self, FingerTree::Empty)
}
/// Pushes `x` onto the front of the tree.
pub fn push_front(self, x: T) -> Self {
match self {
FingerTree::Empty => FingerTree::Single(x),
FingerTree::Single(y) => {
FingerTree::Deep(Digit::One(x), Box::new(FingerTree::Empty), Digit::One(y))
}
FingerTree::Deep(left, spine, right) => match left {
Digit::One(a) => FingerTree::Deep(Digit::Two(x, a), spine, right),
Digit::Two(a, b) => FingerTree::Deep(Digit::Three(x, a, b), spine, right),
Digit::Three(a, b, c) => FingerTree::Deep(Digit::Four(x, a, b, c), spine, right),
Digit::Four(a, b, c, d) => {
let overflow = Node::Node3(b, c, d);
let spine = Box::new(spine.push_front(overflow));
FingerTree::Deep(Digit::Two(x, a), spine, right)
}
},
}
}
/// Pushes `x` onto the back of the tree.
pub fn push_back(self, x: T) -> Self {
match self {
FingerTree::Empty => FingerTree::Single(x),
FingerTree::Single(y) => {
FingerTree::Deep(Digit::One(y), Box::new(FingerTree::Empty), Digit::One(x))
}
FingerTree::Deep(left, spine, right) => match right {
Digit::One(a) => FingerTree::Deep(left, spine, Digit::Two(a, x)),
Digit::Two(a, b) => FingerTree::Deep(left, spine, Digit::Three(a, b, x)),
Digit::Three(a, b, c) => FingerTree::Deep(left, spine, Digit::Four(a, b, c, x)),
Digit::Four(a, b, c, d) => {
let overflow = Node::Node3(a, b, c);
let spine = Box::new(spine.push_back(overflow));
FingerTree::Deep(left, spine, Digit::Two(d, x))
}
},
}
}
/// Flattens the tree into a `Vec` in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
FingerTree::Empty => Vec::new(),
FingerTree::Single(x) => vec![x],
FingerTree::Deep(left, spine, right) => {
let mut out = left.into_vec();
for node in spine.into_vec() {
out.extend(node.into_vec());
}
out.extend(right.into_vec());
out
}
}
}
}
impl<T> Digit<T> {
/// Consumes the digit and returns its elements in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
Digit::One(a) => vec![a],
Digit::Two(a, b) => vec![a, b],
Digit::Three(a, b, c) => vec![a, b, c],
Digit::Four(a, b, c, d) => vec![a, b, c, d],
}
}
}
impl<T> Node<T> {
/// Consumes the node and returns its elements in left-to-right order.
pub fn into_vec(self) -> Vec<T> {
match self {
Node::Node2(a, b) => vec![a, b],
Node::Node3(a, b, c) => vec![a, b, c],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_tree_flattens_to_empty_vec() {
let t: FingerTree<i32> = FingerTree::new();
assert!(t.is_empty());
assert_eq!(t.into_vec(), Vec::<i32>::new());
}
#[test]
fn single_tree_has_one_element() {
let t = FingerTree::new().push_back(42);
assert_eq!(t, FingerTree::Single(42));
}
#[test]
fn mixed_pushes_preserve_order() {
let t = FingerTree::new()
.push_back(1)
.push_back(2)
.push_back(3)
.push_front(0)
.push_back(4)
.push_front(-1);
assert_eq!(t.into_vec(), vec![-1, 0, 1, 2, 3, 4]);
}
#[test]
fn push_back_many_preserves_order() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (1..=10).collect::<Vec<_>>());
}
#[test]
fn push_front_many_reverses_insertion() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_front(x));
assert_eq!(t.into_vec(), (1..=10).rev().collect::<Vec<_>>());
}
#[test]
fn long_sequence_forces_spine_growth() {
let n = 100;
let t = (0..n).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (0..n).collect::<Vec<_>>());
}
#[test]
fn interleaved_pushes_match_vecdeque() {
use std::collections::VecDeque;
let mut t = FingerTree::new();
let mut expected: VecDeque<i32> = VecDeque::new();
for i in 0..50 {
if i % 2 == 0 {
t = t.push_back(i);
expected.push_back(i);
} else {
t = t.push_front(i);
expected.push_front(i);
}
}
assert_eq!(t.into_vec(), Vec::from(expected));
}
#[test]
fn digit_into_vec_returns_elements_in_order() {
assert_eq!(Digit::One(1).into_vec(), vec![1]);
assert_eq!(Digit::Two(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Digit::Three(1, 2, 3).into_vec(), vec![1, 2, 3]);
assert_eq!(Digit::Four(1, 2, 3, 4).into_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn node_into_vec_returns_elements_in_order() {
assert_eq!(Node::Node2(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Node::Node3(1, 2, 3).into_vec(), vec![1, 2, 3]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_tree_flattens_to_empty_vec() {
let t: FingerTree<i32> = FingerTree::new();
assert!(t.is_empty());
assert_eq!(t.into_vec(), Vec::<i32>::new());
}
#[test]
fn single_tree_has_one_element() {
let t = FingerTree::new().push_back(42);
assert_eq!(t, FingerTree::Single(42));
}
#[test]
fn mixed_pushes_preserve_order() {
let t = FingerTree::new()
.push_back(1)
.push_back(2)
.push_back(3)
.push_front(0)
.push_back(4)
.push_front(-1);
assert_eq!(t.into_vec(), vec![-1, 0, 1, 2, 3, 4]);
}
#[test]
fn push_back_many_preserves_order() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (1..=10).collect::<Vec<_>>());
}
#[test]
fn push_front_many_reverses_insertion() {
let t = (1..=10).fold(FingerTree::new(), |acc, x| acc.push_front(x));
assert_eq!(t.into_vec(), (1..=10).rev().collect::<Vec<_>>());
}
#[test]
fn long_sequence_forces_spine_growth() {
let n = 100;
let t = (0..n).fold(FingerTree::new(), |acc, x| acc.push_back(x));
assert_eq!(t.into_vec(), (0..n).collect::<Vec<_>>());
}
#[test]
fn interleaved_pushes_match_vecdeque() {
use std::collections::VecDeque;
let mut t = FingerTree::new();
let mut expected: VecDeque<i32> = VecDeque::new();
for i in 0..50 {
if i % 2 == 0 {
t = t.push_back(i);
expected.push_back(i);
} else {
t = t.push_front(i);
expected.push_front(i);
}
}
assert_eq!(t.into_vec(), Vec::from(expected));
}
#[test]
fn digit_into_vec_returns_elements_in_order() {
assert_eq!(Digit::One(1).into_vec(), vec![1]);
assert_eq!(Digit::Two(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Digit::Three(1, 2, 3).into_vec(), vec![1, 2, 3]);
assert_eq!(Digit::Four(1, 2, 3, 4).into_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn node_into_vec_returns_elements_in_order() {
assert_eq!(Node::Node2(1, 2).into_vec(), vec![1, 2]);
assert_eq!(Node::Node3(1, 2, 3).into_vec(), vec![1, 2, 3]);
}
}
Deep Comparison
Finger Tree — Comparison
Core Insight
A finger tree stores digits (1-4 elements) at each end and a spine of Node elements. When digits overflow (4 items), they're packed into Node3 and pushed into the spine — which is itself a FingerTree<Node<T>>. This nesting (FingerTree<Node<FingerTree<Node<...>>>>) is the key insight. OCaml handles this type-level recursion implicitly; Rust requires Box at each recursive level to bound the size.
OCaml Approach
type 'a finger_tree = Empty | Single of 'a | Deep of 'a digit * 'a node finger_tree * 'a digit'a node finger_tree is a finger_tree parameterized with nodefunction sugarpush_front (Node3 (b,c,d)) spine — recurse into spine with packed nodesRust Approach
FingerTree<T> with Box<FingerTree<Node<T>>> for spineBox required to give the recursive type a known sizeFingerTree<Node<T>> — different type parametermatch *l — deref Box to match inner Digitself in push_front/push_back (value semantics, functional style)Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Recursive type param | 'a node finger_tree | Box<FingerTree<Node<T>>> |
| Boxing | Implicit (GC) | Explicit Box |
| Spine type | 'a node finger_tree | FingerTree<Node<T>> |
| Pattern match Box | n/a | match *l { Digit::One(a) => ... } |
| Push into spine | push_front (Node3 (b,c,d)) spine | spine.push_front(Node::Node3(b,c,d)) |
| Value vs reference | Return new value | Consume self, return new value |
| to_list | digit_to_list @ node_to_list @ ... | Vec + extend |
Exercises
struct TwoFinger<T> { front: Vec<T>, spine: VecDeque<T>, back: Vec<T> } — O(1) push/pop at both ends when fingers are small.split_at(self, idx: usize) -> (Self, Self) for the simplified version.append_all(self, items: impl IntoIterator<Item=T>) -> Self efficiently.Size monoid supports O(log n) random access by index.map method: map<U: Clone, F: Fn(T) -> U>(self, f: F) -> FingerTree<U>.