From 52f814e663a39fe8957ab622dc7ff0dc69c4d6f8 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 20 Oct 2023 18:32:43 -0400 Subject: [PATCH 1/6] Build a basic tree and experiment with traversals --- Cargo.lock | 4 ++ Cargo.toml | 1 + build.sh | 1 + tree/Cargo.toml | 8 ++++ tree/src/lib.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 tree/Cargo.toml create mode 100644 tree/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index aa82149..72c8928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4060,6 +4060,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +[[package]] +name = "tree" +version = "0.1.0" + [[package]] name = "try-lock" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 266bdc5..80b71cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ members = [ "result-extended", "screenplay", "sgf", + "tree", ] diff --git a/build.sh b/build.sh index 4168a1a..dcaed56 100755 --- a/build.sh +++ b/build.sh @@ -23,6 +23,7 @@ RUST_ALL_TARGETS=( "result-extended" "screenplay" "sgf" + "tree" ) build_rust_targets() { diff --git a/tree/Cargo.toml b/tree/Cargo.toml new file mode 100644 index 0000000..7ee0dbe --- /dev/null +++ b/tree/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tree" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tree/src/lib.rs b/tree/src/lib.rs new file mode 100644 index 0000000..77088b0 --- /dev/null +++ b/tree/src/lib.rs @@ -0,0 +1,113 @@ +//! This data structure is a generic tree which can contain any data. That data itself need to keep +//! track of its own tree structure. +//! +//! This surely already exists. I am created it to test my own ability to do things in Rust. + +use std::{cell::RefCell, collections::VecDeque, rc::Rc}; + +#[derive(Debug)] +pub enum Tree { + Empty, + Root(Rc>>), +} + +impl Default for Tree { + fn default() -> Self { + Tree::Empty + } +} + +impl Tree { + pub fn set_value(&mut self, value: T) { + *self = Tree::Root(Rc::new(RefCell::new(Node::new(value)))); + } + + pub fn find_bfs<'a, F>(&'a self, op: F) -> Option>>> + where + F: FnOnce(&T) -> bool + Copy, + { + let mut queue: VecDeque>>> = match self { + Tree::Empty => VecDeque::new(), + Tree::Root(node) => { + let mut queue = VecDeque::new(); + queue.push_back(node.clone()); + queue + } + }; + + while let Some(node) = queue.pop_front() { + if op(&node.borrow().value) { + return Some(node.clone()); + } + + for child in node.borrow().children.iter() { + queue.push_back(child.clone()); + } + } + None + } +} + +#[derive(Debug, PartialEq)] +pub struct Node { + value: T, + children: Vec>>>, +} + +impl Node { + pub fn new(value: T) -> Self { + Self { + value, + children: vec![], + } + } + + pub fn children<'a>(&'a self) -> &'a Vec>>> { + &self.children + } + + pub fn value<'a>(&'a self) -> &'a T { + &self.value + } + + pub fn value_mut<'a>(&'a mut self) -> &'a mut T { + &mut self.value + } + + pub fn add_child_node(&mut self, child: Node) { + self.children.push(Rc::new(RefCell::new(child))); + } + + pub fn add_child_value(&mut self, value: T) { + self.children.push(Rc::new(RefCell::new(Node::new(value)))); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_find_node_in_tree() { + let mut tree = Tree::default(); + tree.set_value(15); + assert!(tree.find_bfs(|val| *val == 15).is_some()); + assert!(tree.find_bfs(|val| *val == 16).is_none()); + + { + let node_ref = tree.find_bfs(|val| *val == 15).unwrap(); + node_ref.borrow_mut().add_child_value(20); + } + + assert!(tree.find_bfs(|val| *val == 20).is_some()); + } + + #[test] + fn node_can_add_children() { + let mut n = Node::new(15); + n.add_child_value(20); + + assert_eq!(n.value, 15); + assert_eq!(n.children, vec![Rc::new(RefCell::new(Node::new(20)))]); + } +} -- 2.44.1 From fbf6a9e76e0432ae26913ebbc0704e7cba066379 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 20 Oct 2023 19:49:31 -0400 Subject: [PATCH 2/6] Move the refcell to inside of the Node --- tree/src/lib.rs | 73 +++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 77088b0..8835d82 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -6,27 +6,28 @@ use std::{cell::RefCell, collections::VecDeque, rc::Rc}; #[derive(Debug)] -pub enum Tree { +pub enum Tree { Empty, - Root(Rc>>), + Root(Node), } -impl Default for Tree { +impl Default for Tree { fn default() -> Self { Tree::Empty } } -impl Tree { +impl Tree { pub fn set_value(&mut self, value: T) { - *self = Tree::Root(Rc::new(RefCell::new(Node::new(value)))); + *self = Tree::Root(Node::new(value)); } - pub fn find_bfs<'a, F>(&'a self, op: F) -> Option>>> + /// Use a breadth-first-search pattern to find a node, returning the node if found. + pub fn find_bfs<'a, F>(&'a self, op: F) -> Option> where F: FnOnce(&T) -> bool + Copy, { - let mut queue: VecDeque>>> = match self { + let mut queue: VecDeque> = match self { Tree::Empty => VecDeque::new(), Tree::Root(node) => { let mut queue = VecDeque::new(); @@ -36,50 +37,54 @@ impl Tree { }; while let Some(node) = queue.pop_front() { - if op(&node.borrow().value) { + if op(&node.value()) { return Some(node.clone()); } - for child in node.borrow().children.iter() { - queue.push_back(child.clone()); + for child in node.children() { + queue.push_back(child.clone()) } } None } } -#[derive(Debug, PartialEq)] -pub struct Node { +// By using the Rc container here, I'm able to make Node easily clonable while still +// having the contents be shared. This means that I can change the tree structure without having to +// make the visible objects mutable. +// +// This feels like cheating the type system. +#[derive(Clone, Debug)] +pub struct Node(Rc>>); + +#[derive(Debug)] +struct Node_ { value: T, - children: Vec>>>, + children: Vec>, } -impl Node { +impl Node { pub fn new(value: T) -> Self { - Self { + Self(Rc::new(RefCell::new(Node_ { value, children: vec![], - } + }))) } - pub fn children<'a>(&'a self) -> &'a Vec>>> { - &self.children + pub fn value<'a>(&'a self) -> T { + self.0.borrow().value.clone() } - pub fn value<'a>(&'a self) -> &'a T { - &self.value + pub fn children<'a>(&'a self) -> Vec> { + self.0.borrow().children.clone() } - pub fn value_mut<'a>(&'a mut self) -> &'a mut T { - &mut self.value + pub fn add_child_node(&self, child: Node) { + self.0.borrow_mut().children.push(child) } - pub fn add_child_node(&mut self, child: Node) { - self.children.push(Rc::new(RefCell::new(child))); - } - - pub fn add_child_value(&mut self, value: T) { - self.children.push(Rc::new(RefCell::new(Node::new(value)))); + pub fn add_child_value(&self, value: T) { + self.0.borrow_mut().children.push(Node::new(value)) } } @@ -94,20 +99,18 @@ mod tests { assert!(tree.find_bfs(|val| *val == 15).is_some()); assert!(tree.find_bfs(|val| *val == 16).is_none()); - { - let node_ref = tree.find_bfs(|val| *val == 15).unwrap(); - node_ref.borrow_mut().add_child_value(20); - } + let node = tree.find_bfs(|val| *val == 15).unwrap(); + node.add_child_value(20); assert!(tree.find_bfs(|val| *val == 20).is_some()); } #[test] fn node_can_add_children() { - let mut n = Node::new(15); + let n = Node::new(15); n.add_child_value(20); - assert_eq!(n.value, 15); - assert_eq!(n.children, vec![Rc::new(RefCell::new(Node::new(20)))]); + assert_eq!(n.value(), 15); + // assert_eq!(n.children(), vec![Rc::new(RefCell::new(Node::new(20)))]); } } -- 2.44.1 From 2ceccbf38d36189c959e53b793150e8a65ad9bd9 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 20 Oct 2023 20:17:33 -0400 Subject: [PATCH 3/6] Remove the Clone constraint from T --- tree/src/lib.rs | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 8835d82..4cfef40 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -3,21 +3,25 @@ //! //! This surely already exists. I am created it to test my own ability to do things in Rust. -use std::{cell::RefCell, collections::VecDeque, rc::Rc}; +use std::{ + cell::{Ref, RefCell}, + collections::VecDeque, + rc::Rc, +}; #[derive(Debug)] -pub enum Tree { +pub enum Tree { Empty, Root(Node), } -impl Default for Tree { +impl Default for Tree { fn default() -> Self { Tree::Empty } } -impl Tree { +impl Tree { pub fn set_value(&mut self, value: T) { *self = Tree::Root(Node::new(value)); } @@ -41,7 +45,7 @@ impl Tree { return Some(node.clone()); } - for child in node.children() { + for child in node.children().iter() { queue.push_back(child.clone()) } } @@ -54,16 +58,25 @@ impl Tree { // make the visible objects mutable. // // This feels like cheating the type system. -#[derive(Clone, Debug)] -pub struct Node(Rc>>); +// +// However, since I've moved the RefCell inside of the node, I can borrow the node multiple times +// in a traversal function and I can make changes to nodes that I find. +#[derive(Debug)] +pub struct Node(Rc>>); + +impl Clone for Node { + fn clone(&self) -> Self { + Node(Rc::clone(&self.0)) + } +} #[derive(Debug)] -struct Node_ { +struct Node_ { value: T, children: Vec>, } -impl Node { +impl Node { pub fn new(value: T) -> Self { Self(Rc::new(RefCell::new(Node_ { value, @@ -71,12 +84,15 @@ impl Node { }))) } - pub fn value<'a>(&'a self) -> T { - self.0.borrow().value.clone() + // I am copying the value because I don't have a mechanism for keeping the borrow ref active. + // My next step is to figure out how to keep the borrow ref active so that I don't have to + // clone the item. Then I could drop the Clone constraint. + pub fn value<'a>(&'a self) -> Ref { + Ref::map(self.0.borrow(), |v| &v.value) } - pub fn children<'a>(&'a self) -> Vec> { - self.0.borrow().children.clone() + pub fn children<'a>(&'a self) -> Ref>> { + Ref::map(self.0.borrow(), |v| &v.children) } pub fn add_child_node(&self, child: Node) { @@ -110,7 +126,7 @@ mod tests { let n = Node::new(15); n.add_child_value(20); - assert_eq!(n.value(), 15); + assert_eq!(*n.value(), 15); // assert_eq!(n.children(), vec![Rc::new(RefCell::new(Node::new(20)))]); } } -- 2.44.1 From c2e78d7c540402a6a0c48b6b38153e57860446c3 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 20 Oct 2023 20:28:36 -0400 Subject: [PATCH 4/6] Clean up some unnecessary references --- tree/src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 4cfef40..7341b7f 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -27,7 +27,7 @@ impl Tree { } /// Use a breadth-first-search pattern to find a node, returning the node if found. - pub fn find_bfs<'a, F>(&'a self, op: F) -> Option> + pub fn find_bfs(&self, op: F) -> Option> where F: FnOnce(&T) -> bool + Copy, { @@ -84,14 +84,15 @@ impl Node { }))) } - // I am copying the value because I don't have a mechanism for keeping the borrow ref active. - // My next step is to figure out how to keep the borrow ref active so that I don't have to - // clone the item. Then I could drop the Clone constraint. - pub fn value<'a>(&'a self) -> Ref { + /// Immutably retrieve the data in this node. + pub fn value<'a>(&self) -> Ref { + // Ref::map is not actually a member function. I don't know why this was done, other than + // maybe to avoid conflicting with other `map` declarations. Why that is necessary when + // Option::map exists as a member, I don't know. Ref::map(self.0.borrow(), |v| &v.value) } - pub fn children<'a>(&'a self) -> Ref>> { + pub fn children<'a>(&self) -> Ref>> { Ref::map(self.0.borrow(), |v| &v.children) } -- 2.44.1 From 0fbfb4f1adbc4a4a728b4a4dfb55218905c3334a Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 20 Oct 2023 23:43:47 -0400 Subject: [PATCH 5/6] Add a tree map operation --- tree/src/lib.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 7341b7f..8d50e9b 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -9,7 +9,7 @@ use std::{ rc::Rc, }; -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Tree { Empty, Root(Node), @@ -22,8 +22,16 @@ impl Default for Tree { } impl Tree { - pub fn set_value(&mut self, value: T) { - *self = Tree::Root(Node::new(value)); + pub fn new(value: T) -> (Tree, Node) { + let node = Node::new(value); + let tree = Tree::Root(node.clone()); + (tree, node) + } + + pub fn set_value(&mut self, value: T) -> Node { + let node = Node::new(value); + *self = Tree::Root(node.clone()); + node } /// Use a breadth-first-search pattern to find a node, returning the node if found. @@ -51,6 +59,33 @@ impl Tree { } None } + + pub fn map(&self, op: F) -> Tree + where + F: FnOnce(&Node) -> Node + Copy, + { + match self { + Tree::Empty => Tree::Empty, + Tree::Root(root) => { + let new_root = op(root); + let mut queue: VecDeque<(Node, Node)> = root + .children() + .iter() + .map(|child| (child.clone(), new_root.clone())) + .collect(); + + while let Some((source, dest)) = queue.pop_front() { + let res = op(&source); + dest.add_child_node(res.clone()); + + for child in source.children().iter() { + queue.push_back((child.clone(), res.clone())); + } + } + Tree::Root(new_root) + } + } + } } // By using the Rc container here, I'm able to make Node easily clonable while still @@ -100,8 +135,10 @@ impl Node { self.0.borrow_mut().children.push(child) } - pub fn add_child_value(&self, value: T) { - self.0.borrow_mut().children.push(Node::new(value)) + pub fn add_child_value(&self, value: T) -> Node { + let node = Node::new(value); + self.0.borrow_mut().children.push(node.clone()); + node } } @@ -130,4 +167,17 @@ mod tests { assert_eq!(*n.value(), 15); // assert_eq!(n.children(), vec![Rc::new(RefCell::new(Node::new(20)))]); } + + #[test] + fn it_can_map_one_tree_to_another() { + let (tree, n) = Tree::new(15); + let n = n.add_child_value(16); + let _ = n.add_child_value(17); + + let tree2 = tree.map(|v| Node::new(v.value().to_string())); + + assert!(tree2.find_bfs(|val| *val == "15").is_some()); + assert!(tree2.find_bfs(|val| *val == "16").is_some()); + assert!(tree2.find_bfs(|val| *val == "17").is_some()); + } } -- 2.44.1 From c2e34db79c0f3f357c0845ec63202d670ed057e1 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 24 Oct 2023 23:05:02 -0400 Subject: [PATCH 6/6] Map on the data within the node instead of the node itself --- tree/src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tree/src/lib.rs b/tree/src/lib.rs index 8d50e9b..2b9cdc8 100644 --- a/tree/src/lib.rs +++ b/tree/src/lib.rs @@ -60,14 +60,23 @@ impl Tree { None } + /// Convert each node of a tree from type T to type U pub fn map(&self, op: F) -> Tree where - F: FnOnce(&Node) -> Node + Copy, + F: FnOnce(&T) -> U + Copy, { + // A key part of this is to avoid recursion. There is no telling how deep a tree may go (Go + // game records can go hundreds of nodes deep), so we're going to just avoid recursion. match self { Tree::Empty => Tree::Empty, Tree::Root(root) => { - let new_root = op(root); + let new_root = Node::new(op(&root.value())); + + // This queue serves as a work list. Each node in the queue needs to be converted, + // and I've paired the node up with the one that it's supposed to be attached to. + // So, as we look at a node A, we make sure that all of its children gets added to + // the queue, and that the queue knows that the conversion of each child node + // should get attached to A. let mut queue: VecDeque<(Node, Node)> = root .children() .iter() @@ -75,7 +84,7 @@ impl Tree { .collect(); while let Some((source, dest)) = queue.pop_front() { - let res = op(&source); + let res = Node::new(op(&source.value())); dest.add_child_node(res.clone()); for child in source.children().iter() { @@ -174,7 +183,7 @@ mod tests { let n = n.add_child_value(16); let _ = n.add_child_value(17); - let tree2 = tree.map(|v| Node::new(v.value().to_string())); + let tree2 = tree.map(|v| v.to_string()); assert!(tree2.find_bfs(|val| *val == "15").is_some()); assert!(tree2.find_bfs(|val| *val == "16").is_some()); -- 2.44.1