ExamplesBy LevelBy TopicLearning Paths
973 Advanced

973 Finger Tree

Functional Programming

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

  • • Understand the finger tree structure: two "fingers" (small buffers at each end) + a lazy spine
  • • Recognize the recursive type problem: FingerTree<Node<T>> is not a finite type in Rust without boxing
  • • Implement a simplified finger tree API using VecDeque with consuming push/pop methods
  • • Apply consuming (value-taking) methods: push_front(self, x) -> Self returns a new tree
  • • Understand when VecDeque is sufficient vs when a true finger tree adds value
  • Code Example

    #![allow(clippy::all)]
    // 973: Finger Tree (Simplified)
    // Deque with O(1) amortized push/pop both ends
    //
    // The classic finger tree uses `FingerTree<Node<T>>` for the spine,
    // which creates an infinitely recursive type in Rust. We solve this
    // by using a type-erased spine (Vec-based deque internally).
    
    use std::collections::VecDeque;
    
    /// A simplified finger tree that provides O(1) amortized push/pop at both ends.
    /// Internally uses a VecDeque for the spine to avoid recursive type issues.
    #[derive(Debug, Clone)]
    pub struct FingerTree<T> {
        deque: VecDeque<T>,
    }
    
    impl<T: Clone> FingerTree<T> {
        pub fn empty() -> Self {
            FingerTree {
                deque: VecDeque::new(),
            }
        }
    
        pub fn push_front(mut self, x: T) -> Self {
            self.deque.push_front(x);
            self
        }
    
        pub fn push_back(mut self, x: T) -> Self {
            self.deque.push_back(x);
            self
        }
    
        pub fn pop_front(mut self) -> (Option<T>, Self) {
            let item = self.deque.pop_front();
            (item, self)
        }
    
        pub fn pop_back(mut self) -> (Option<T>, Self) {
            let item = self.deque.pop_back();
            (item, self)
        }
    
        pub fn peek_front(&self) -> Option<&T> {
            self.deque.front()
        }
    
        pub fn peek_back(&self) -> Option<&T> {
            self.deque.back()
        }
    
        pub fn is_empty(&self) -> bool {
            self.deque.is_empty()
        }
    
        pub fn len(&self) -> usize {
            self.deque.len()
        }
    
        pub fn to_vec(&self) -> Vec<T> {
            self.deque.iter().cloned().collect()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_push_back_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_push_front_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_front(x));
            assert_eq!(t.to_vec(), vec![5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_mixed_push() {
            let t = FingerTree::empty()
                .push_back(1)
                .push_back(2)
                .push_back(3)
                .push_front(0)
                .push_back(4)
                .push_front(-1);
            assert_eq!(t.to_vec(), vec![-1, 0, 1, 2, 3, 4]);
        }
    
        #[test]
        fn test_longer_sequence() {
            let t = (1..=10).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), (1..=10).collect::<Vec<_>>());
        }
    
        #[test]
        fn test_empty() {
            let t: FingerTree<i32> = FingerTree::empty();
            assert_eq!(t.to_vec(), Vec::<i32>::new());
        }
    
        #[test]
        fn test_single() {
            let t = FingerTree::empty().push_back(42);
            assert_eq!(t.to_vec(), vec![42]);
        }
    }

    Key Differences

    AspectRustOCaml
    Recursive typeCannot express FingerTree<Node<T>> without boxingNatural recursive type in Haskell/OCaml
    Consuming APIfn push(self) -> SelfPersistent { t with ... }
    Practical dequeVecDeque — O(1) amortizedTwo-list or stdlib Queue
    True finger treeRequires Box<...> indirectionData.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

    #![allow(clippy::all)]
    // 973: Finger Tree (Simplified)
    // Deque with O(1) amortized push/pop both ends
    //
    // The classic finger tree uses `FingerTree<Node<T>>` for the spine,
    // which creates an infinitely recursive type in Rust. We solve this
    // by using a type-erased spine (Vec-based deque internally).
    
    use std::collections::VecDeque;
    
    /// A simplified finger tree that provides O(1) amortized push/pop at both ends.
    /// Internally uses a VecDeque for the spine to avoid recursive type issues.
    #[derive(Debug, Clone)]
    pub struct FingerTree<T> {
        deque: VecDeque<T>,
    }
    
    impl<T: Clone> FingerTree<T> {
        pub fn empty() -> Self {
            FingerTree {
                deque: VecDeque::new(),
            }
        }
    
        pub fn push_front(mut self, x: T) -> Self {
            self.deque.push_front(x);
            self
        }
    
        pub fn push_back(mut self, x: T) -> Self {
            self.deque.push_back(x);
            self
        }
    
        pub fn pop_front(mut self) -> (Option<T>, Self) {
            let item = self.deque.pop_front();
            (item, self)
        }
    
        pub fn pop_back(mut self) -> (Option<T>, Self) {
            let item = self.deque.pop_back();
            (item, self)
        }
    
        pub fn peek_front(&self) -> Option<&T> {
            self.deque.front()
        }
    
        pub fn peek_back(&self) -> Option<&T> {
            self.deque.back()
        }
    
        pub fn is_empty(&self) -> bool {
            self.deque.is_empty()
        }
    
        pub fn len(&self) -> usize {
            self.deque.len()
        }
    
        pub fn to_vec(&self) -> Vec<T> {
            self.deque.iter().cloned().collect()
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_push_back_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_push_front_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_front(x));
            assert_eq!(t.to_vec(), vec![5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_mixed_push() {
            let t = FingerTree::empty()
                .push_back(1)
                .push_back(2)
                .push_back(3)
                .push_front(0)
                .push_back(4)
                .push_front(-1);
            assert_eq!(t.to_vec(), vec![-1, 0, 1, 2, 3, 4]);
        }
    
        #[test]
        fn test_longer_sequence() {
            let t = (1..=10).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), (1..=10).collect::<Vec<_>>());
        }
    
        #[test]
        fn test_empty() {
            let t: FingerTree<i32> = FingerTree::empty();
            assert_eq!(t.to_vec(), Vec::<i32>::new());
        }
    
        #[test]
        fn test_single() {
            let t = FingerTree::empty().push_back(42);
            assert_eq!(t.to_vec(), vec![42]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_push_back_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), vec![1, 2, 3, 4, 5]);
        }
    
        #[test]
        fn test_push_front_order() {
            let t = (1..=5).fold(FingerTree::empty(), |acc, x| acc.push_front(x));
            assert_eq!(t.to_vec(), vec![5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_mixed_push() {
            let t = FingerTree::empty()
                .push_back(1)
                .push_back(2)
                .push_back(3)
                .push_front(0)
                .push_back(4)
                .push_front(-1);
            assert_eq!(t.to_vec(), vec![-1, 0, 1, 2, 3, 4]);
        }
    
        #[test]
        fn test_longer_sequence() {
            let t = (1..=10).fold(FingerTree::empty(), |acc, x| acc.push_back(x));
            assert_eq!(t.to_vec(), (1..=10).collect::<Vec<_>>());
        }
    
        #[test]
        fn test_empty() {
            let t: FingerTree<i32> = FingerTree::empty();
            assert_eq!(t.to_vec(), Vec::<i32>::new());
        }
    
        #[test]
        fn test_single() {
            let t = FingerTree::empty().push_back(42);
            assert_eq!(t.to_vec(), vec![42]);
        }
    }

    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
  • • The spine 'a node finger_tree is a finger_tree parameterized with node
  • • No explicit boxing — OCaml values are pointer-sized by default
  • • Pattern matching with function sugar
  • push_front (Node3 (b,c,d)) spine — recurse into spine with packed nodes
  • Rust Approach

  • FingerTree<T> with Box<FingerTree<Node<T>>> for spine
  • Box required to give the recursive type a known size
  • • Each method call on spine is typed as FingerTree<Node<T>> — different type parameter
  • match *l — deref Box to match inner Digit
  • • Consuming self in push_front/push_back (value semantics, functional style)
  • Comparison Table

    AspectOCamlRust
    Recursive type param'a node finger_treeBox<FingerTree<Node<T>>>
    BoxingImplicit (GC)Explicit Box
    Spine type'a node finger_treeFingerTree<Node<T>>
    Pattern match Boxn/amatch *l { Digit::One(a) => ... }
    Push into spinepush_front (Node3 (b,c,d)) spinespine.push_front(Node::Node3(b,c,d))
    Value vs referenceReturn new valueConsume self, return new value
    to_listdigit_to_list @ node_to_list @ ...Vec + extend

    Exercises

  • Implement a true two-finger tree: struct TwoFinger<T> { front: Vec<T>, spine: VecDeque<T>, back: Vec<T> } — O(1) push/pop at both ends when fingers are small.
  • Implement split_at(self, idx: usize) -> (Self, Self) for the simplified version.
  • Implement append_all(self, items: impl IntoIterator<Item=T>) -> Self efficiently.
  • Show how a finger tree with a Size monoid supports O(log n) random access by index.
  • Implement a map method: map<U: Clone, F: Fn(T) -> U>(self, f: F) -> FingerTree<U>.
  • Open Source Repos