Skip to content

Commit

Permalink
Make functional BST more-functional.
Browse files Browse the repository at this point in the history
**This Commit**
Adds history to the functional BST and no longer requires ownership to
`insert`/`delete`.

**Why?**
Mainly to make benchmarking easier but this has the benefit of more
closely matching a functional data structure in that, when new trees are
created from "mutating" operations, the history of trees/operations is
still accessible.
  • Loading branch information
mlodato517 committed Oct 21, 2021
1 parent f77b68b commit 7cee4c3
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 54 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,18 @@ jobs:
with:
command: clippy
args: -- -D warnings

docs:
name: Rustdoc
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps
164 changes: 110 additions & 54 deletions src/functional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,44 @@
//! expect to modify the tree (e.g. `insert` or `delete`) instead return
//! a new tree that reference many of the nodes of the original tree.
//!
//! To avoid copious `Rc`ing, we do not implement a particularly efficient
//! persistent structure - we only allow one tree at a time. Still, most
//! of the algorithms are the same and there are useful lessons to learn!
//! # Examples
//!
//! ```
//! use bst::functional::Tree;
//!
//! let tree = Tree::new();
//!
//! // Nothing in here yet.
//! assert_eq!(tree.find(&1), None);
//!
//! // This `insert` returns a new tree!
//! let new_tree = tree.insert(1, 2);
//!
//! // The new tree has this new value but the old one doesn't.
//! assert_eq!(new_tree.find(&1), Some(&2));
//! assert_eq!(tree.find(&1), None);
//!
//! // Insert a new value for the same key gives yet another tree.
//! let newer_tree = new_tree.insert(1, 3);
//!
//! // And delete it for good measure.
//! let newest_tree = newer_tree.delete(&1);
//!
//! // All history is preserved.
//! assert_eq!(newest_tree.find(&1), None);
//! assert_eq!(newer_tree.find(&1), Some(&3));
//! assert_eq!(new_tree.find(&1), Some(&2));
//! assert_eq!(tree.find(&1), None);
//! ```
use std::cmp;
use std::rc::Rc;

/// A Binary Search Tree. This can be used for inserting, finding,
/// and deleting keys and values. Note that this data structure is
/// functional - operations that would modify the tree instead
/// return a new tree.
#[derive(Clone)]
pub enum Tree<K, V> {
/// A marker for the empty pointer at the bottom of a subtree.
Leaf,
Expand All @@ -34,37 +62,46 @@ impl<K, V> Tree<K, V> {
}

/// Returns a new tree that includes a node
/// containing the given key and value
/// containing the given key and value.
///
/// # Examples
///
/// ```
/// use bst::functional::Tree;
///
/// let mut tree = Tree::new();
/// tree = tree.insert(1, 2);
///
/// assert_eq!(tree.find(&1), Some(&2));
///
/// tree = tree.insert(1, 3);
/// let tree = Tree::new();
/// let new_tree = tree.insert(1, 2);
/// let newer_tree = new_tree.insert(1, 3);
///
/// assert_eq!(tree.find(&1), Some(&3));
/// // All history is preserved.
/// assert_eq!(newer_tree.find(&1), Some(&3));
/// assert_eq!(new_tree.find(&1), Some(&2));
/// assert_eq!(tree.find(&1), None);
/// ```
pub fn insert(self, key: K, value: V) -> Self
pub fn insert(&self, key: K, value: V) -> Self
where
K: cmp::Ord,
{
match self {
Tree::Leaf => Tree::Node(Node::new(key, value)),
Tree::Node(n) => match key.cmp(&n.key) {
cmp::Ordering::Less => Tree::Node(Node {
left: Box::new(n.left.insert(key, value)),
..n
left: Rc::new(n.left.insert(key, value)),
right: n.right.clone(),
key: n.key.clone(),
value: n.value.clone(),
}),
cmp::Ordering::Equal => Tree::Node(Node {
value: Rc::new(value),
right: n.right.clone(),
key: n.key.clone(),
left: n.left.clone(),
}),
cmp::Ordering::Equal => Tree::Node(Node { value, ..n }),
cmp::Ordering::Greater => Tree::Node(Node {
right: Box::new(n.right.insert(key, value)),
..n
right: Rc::new(n.right.insert(key, value)),
left: n.left.clone(),
key: n.key.clone(),
value: n.value.clone(),
}),
},
}
Expand All @@ -79,8 +116,8 @@ impl<K, V> Tree<K, V> {
/// ```
/// use bst::functional::Tree;
///
/// let mut tree = Tree::new();
/// tree = tree.insert(1, 2);
/// let tree = Tree::new();
/// let tree = tree.insert(1, 2);
///
/// assert_eq!(tree.find(&1), Some(&2));
/// assert_eq!(tree.find(&42), None);
Expand Down Expand Up @@ -109,87 +146,106 @@ impl<K, V> Tree<K, V> {
/// ```
/// use bst::functional::Tree;
///
/// let mut tree = Tree::new();
/// tree = tree.insert(1, 2);
///
/// tree = tree.delete(&1);
/// let tree = Tree::new();
/// let tree = tree.insert(1, 2);
/// let newer_tree = tree.delete(&1);
///
/// assert_eq!(tree.find(&1), None);
/// // All history is preserved.
/// assert_eq!(newer_tree.find(&1), None);
/// assert_eq!(tree.find(&1), Some(&2));
/// ```
pub fn delete(self, k: &K) -> Self
pub fn delete(&self, k: &K) -> Self
where
K: cmp::Ord,
{
match self {
Tree::Leaf => self,
Tree::Leaf => Tree::Leaf,
Tree::Node(n) => match k.cmp(&n.key) {
cmp::Ordering::Less => Tree::Node(Node {
left: Box::new(n.left.delete(k)),
..n
left: Rc::new(n.left.delete(k)),
right: n.right.clone(),
key: n.key.clone(),
value: n.value.clone(),
}),
cmp::Ordering::Equal => match (*n.left, *n.right) {
(Tree::Leaf, right_child) => right_child,
(left_child, Tree::Leaf) => left_child,
cmp::Ordering::Equal => match (n.left.as_ref(), n.right.as_ref()) {
(Tree::Leaf, Tree::Leaf) => Tree::Leaf,
(Tree::Leaf, Tree::Node(right)) => Tree::Node(right.clone()),
(Tree::Node(left), Tree::Leaf) => Tree::Node(left.clone()),

// If we have two children we have to figure out
// which node to promote. We choose here this node's
// predecessor. That is, the largest node in this node's
// left subtree.
(Tree::Node(left_child), right_child) => {
(Tree::Node(left_child), _) => {
let (pred_key, pred_val, new_left) = left_child.delete_largest();
Tree::Node(Node {
left: new_left,
right: Box::new(right_child), // I really don't want this allocation here
right: n.right.clone(),
key: pred_key,
value: pred_val,
})
}
},
cmp::Ordering::Greater => Tree::Node(Node {
right: Box::new(n.right.delete(k)),
..n
right: Rc::new(n.right.delete(k)),
left: n.left.clone(),
key: n.key.clone(),
value: n.value.clone(),
}),
},
}
}
}

/// A `Node` tree has a key that is used for searching/sorting and a value
/// A `Node` has a key that is used for searching/sorting and a value
/// that is associated with that key. It always has two children although
/// those children may be [`Leaf`][Tree::Leaf]s.
pub struct Node<K, V> {
key: K,
value: V,
left: Box<Tree<K, V>>,
right: Box<Tree<K, V>>,
key: Rc<K>,
value: Rc<V>,
left: Rc<Tree<K, V>>,
right: Rc<Tree<K, V>>,
}

/// Manual implementation of `Clone` so we don't clone references when the generic parameters
/// aren't `Clone` themselves.
///
/// Note the comment on generic structs in
/// [the docs][<https://doc.rust-lang.org/std/clone/trait.Clone.html#derivable>].
impl<K, V> Clone for Node<K, V> {
fn clone(&self) -> Self {
Self {
key: self.key.clone(),
value: self.value.clone(),
left: self.left.clone(),
right: self.right.clone(),
}
}
}

impl<K, V> Node<K, V> {
/// Construct a new `Node` with the given `key` and `value.
fn new(key: K, value: V) -> Self {
Self {
key,
value,
left: Box::new(Tree::Leaf),
right: Box::new(Tree::Leaf),
key: Rc::new(key),
value: Rc::new(value),
left: Rc::new(Tree::Leaf),
right: Rc::new(Tree::Leaf),
}
}

/// Returns the largest node and a new subtree
/// without that largest node.
fn delete_largest(self) -> (K, V, Box<Tree<K, V>>)
/// Returns the key and value of the largest node and a new subtree without that largest node.
fn delete_largest(&self) -> (Rc<K>, Rc<V>, Rc<Tree<K, V>>)
where
K: cmp::Ord,
{
match *self.right {
Tree::Leaf => (self.key, self.value, self.left),
match self.right.as_ref() {
Tree::Leaf => (self.key.clone(), self.value.clone(), self.left.clone()),
Tree::Node(r) => {
let (key, value, sub) = r.delete_largest();
let rest = self.clone();

(
key,
value,
Box::new(Tree::Node(Node { right: sub, ..self })),
)
(key, value, Rc::new(Tree::Node(Node { right: sub, ..rest })))
}
}
}
Expand Down

0 comments on commit 7cee4c3

Please sign in to comment.