diff --git a/.artifacts/license/APACHE.LICENSE b/.artifacts/license/APACHE.LICENSE index 94cb917..b9f3f83 100644 --- a/.artifacts/license/APACHE.LICENSE +++ b/.artifacts/license/APACHE.LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2022 Scattered-Systems + Copyright 2023 Scattered-Systems, DAO LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/.artifacts/license/MIT.LICENSE b/.artifacts/license/MIT.LICENSE index ff3191a..a72851f 100644 --- a/.artifacts/license/MIT.LICENSE +++ b/.artifacts/license/MIT.LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2022 Joe McCain III +Copyright (c) 2023 Scattered-Systems, DAO LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 59ea3db..173af4d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,9 @@ version: 2 updates: + - package-ecosystem: cargo + directory: / + schedule: + interval: daily - package-ecosystem: github-actions directory: / schedule: diff --git a/.gitpod.yml b/.gitpod.yml index 7e55747..1c0938e 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,6 +1,8 @@ tasks: - init: | - J + sudo update -y && sudo upgrade -y && sudo autoremove -y + rustup default nightly + rustup target add wasm32-unknown-unknown wasm32-wasi --toolchain nightly cargo build --release --target wasm32-unknown-unknown --workspace command: cargo watch -x test --all diff --git a/Cargo.toml b/Cargo.toml index 33188c8..5f020ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,25 @@ +[workspace.package] +authors = ["FL03 (https://gitlab.com/FL03)", "Scattered-Systems (https://gitlab.com/scsys)"] +categories = [] +description = "Algae is a collection of core algorithms and data-structures, written in Rust" +edition = "2021" +homepage = "https://github.com/scattered-systems/algae/wiki" +keywords = ["algorithms", "data-structures"] +license = "Apache-2.0" +readme = "README.md" +repository = "https://github.com/scattered-systems/algae" +version = "0.1.18" + +[workspace.dependencies] +decanter = { features = ["derive", "wasm"], version = "0.1.3" } +scsys = { features = [], version = "0.1.41" } + +itertools = "0.10" +serde = { features = ["derive"], version = "1" } +serde_json = "1" +smart-default = "0.6" +strum = { features = ["derive"], version = "0.24" } + [workspace] default-members = [ "algae" diff --git a/LICENSE b/LICENSE index 94cb917..f25b2b1 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2022 Scattered-Systems + Copyright 2023 Joe McCain III Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index 4c6b246..58fd565 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,32 @@ # algae -[![Rust Clippy](https://github.com/scattered-systems/algae/actions/workflows/rust-clippy.yml/badge.svg)](https://github.com/scattered-systems/algae/actions/workflows/rust-clippy.yml) -[![Rust](https://github.com/scattered-systems/algae/actions/workflows/rust.yml/badge.svg)](https://github.com/scattered-systems/algae/actions/workflows/rust.yml) +[![crates.io](https://img.shields.io/crates/v/algae.svg)](https://crates.io/crates/algae) +[![docs.rs](https://docs.rs/algae/badge.svg)](https://docs.rs/algae) +[![Clippy](https://github.com/FL03/algae/actions/workflows/rust-clippy.yml/badge.svg)](https://github.com/FL03/algae/actions/workflows/rust-clippy.yml) +[![Rust](https://github.comFL03/algae/actions/workflows/rust.yml/badge.svg)](https://github.com/FL03/algae/actions/workflows/rust.yml) + +*** + +Welcome to algae, a collection of optimized data-structures and algorithms intended for use within blockchain environments. ## Developers ### Getting Started - git clone https://github.com/scattered-systems/algae + git clone https://github.com/FL03/algae #### Testing cargo build --release cargo test --all-features --release -### Resources +## Contributors + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to update tests as appropriate. -* [Crate.io](https://crates.io/crates/algae) -* [Docs](https://docs.rs/algae) +## License -#### _References_ \ No newline at end of file +- [Apache-2.0](https://choosealicense.com/licenses/apache-2.0/) +- [MIT](https://choosealicense.com/licenses/mit/) diff --git a/algae/Cargo.toml b/algae/Cargo.toml index bb0ece4..ab1083b 100644 --- a/algae/Cargo.toml +++ b/algae/Cargo.toml @@ -1,51 +1,60 @@ [package] -authors = ["FL03 (https://gitlab.com/FL03)", "Scattered-Systems (https://gitlab.com/scsys)"] +authors.workspace = true categories = [] -description = "Algae is a collection of core algorithms and data-structures, written in Rust" -edition = "2021" -homepage = "https://github.com/scattered-systems/algae/wiki" +description.workspace = true +edition.workspace = true +homepage.workspace = true keywords = ["algorithms", "data-structures"] -license = "Apache-2.0" +license.workspace = true name = "algae" -readme = "README.md" -repository = "https://github.com/scattered-systems/algae" -version = "0.1.17" # TODO - Update the cargo package version +readme.workspace = true +repository.workspace = true +version.workspace = true [features] -default = ["core", "graph", "merkle", "mmr"] -full = ["core", "graph", "merkle", "mmr"] +default = ["core"] + +core = [ + "merkle", + "mmr", + "graph" +] -core = ["merkle", "mmr"] graph = ["algae-graph"] merkle = ["algae-merkle"] mmr = ["algae-mmr"] +wasm = [] + [lib] bench = false crate-type = ["cdylib", "rlib"] test = true -[dependencies] -scsys = { features = ["full"], version = "0.1.40" } -serde = { features = ["derive"], version = "1" } -serde_json = "1" - -[dependencies.algae-graph] -optional = true -path = "../graph" -version = "0.1.17" - -[dependencies.algae-merkle] -optional = true -path = "../merkle" -version = "0.1.17" - -[dependencies.algae-mmr] -optional = true -path = "../mmr" -version = "0.1.17" +[build-dependencies] +[dependencies] +algae-graph = { features = [], optional = true, path = "../graph", version = "0.1.18" } +algae-merkle = { features = [], optional = true, path = "../merkle", version = "0.1.18" } +algae-mmr = { features = [], optional = true, path = "../mmr", version = "0.1.18" } + +decanter.workspace = true +itertools.workspace = true +scsys.workspace = true +serde.workspace = true +serde_json.workspace = true +smart-default.workspace = true +strum.workspace = true + +[dev-dependencies] +decanter.workspace = true +hex-literal = "0.3" +vrf = "0.2" [package.metadata.docs.rs] all-features = true rustc-args = ["--cfg", "docsrs"] + +[target.wasm32-unknown-unknown] + +[target.wasm32-wasi] diff --git a/algae/README.md b/algae/README.md deleted file mode 100644 index 5b0d98a..0000000 --- a/algae/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Algae - -Algae boasts a collection of useful algorithms and data-structures - -## Getting Started - - git clone https://github.com/scattered-systems/algae.git - cargo test --all-features - -### Examples \ No newline at end of file diff --git a/algae/src/lib.rs b/algae/src/lib.rs index d0102da..f48e1e2 100644 --- a/algae/src/lib.rs +++ b/algae/src/lib.rs @@ -1,6 +1,6 @@ /* Appellation: algae - Creator: FL03 + Contrib: FL03 Description: Algae is a comprehensive collection of algorithms and data-structures */ @@ -11,7 +11,7 @@ pub use algae_merkle as merkle; #[cfg(feature = "mmr")] pub use algae_mmr as mmr; -pub mod trees; +pub mod list; pub mod prelude { #[cfg(feature = "graph")] diff --git a/algae/src/list/linked/mod.rs b/algae/src/list/linked/mod.rs new file mode 100644 index 0000000..5327b5b --- /dev/null +++ b/algae/src/list/linked/mod.rs @@ -0,0 +1,7 @@ +/* + Appellation: linked + Contrib: FL03 + Description: ... Summary ... +*/ + +pub mod persistant; diff --git a/algae/src/list/linked/persistant.rs b/algae/src/list/linked/persistant.rs new file mode 100644 index 0000000..54ef537 --- /dev/null +++ b/algae/src/list/linked/persistant.rs @@ -0,0 +1,130 @@ +/* + Appellation: persistant + Contrib: FL03 + Description: ... Summary ... +*/ +use std::rc::Rc; + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +impl Node { + pub fn new(elem: T, next: Link) -> Self { + Self { elem, next } + } + pub fn data(&self) -> &T { + &self.elem + } + pub fn link(&self) -> &Link { + &self.next + } +} + +/// Singly-Linked, Persistant List +pub struct SLPList { + head: Link, +} + +impl SLPList { + pub fn new() -> Self { + Self::from(None) + } + pub fn prepend(&self, elem: T) -> Self { + SLPList::from(Some(Rc::new(Node::new(elem, self.head.clone())))) + } + + pub fn tail(&self) -> Self { + SLPList::from(self.head.as_ref().and_then(|node| node.link().clone())) + } + + pub fn head(&self) -> Option<&T> { + self.head.as_ref().map(|node| node.data()) + } + + pub fn iter(&self) -> Iter<'_, T> { + Iter { + next: self.head.as_deref(), + } + } +} + +impl From> for SLPList { + fn from(head: Link) -> SLPList { + SLPList { head } + } +} + +impl Default for SLPList { + fn default() -> Self { + Self::from(None) + } +} + +impl Drop for SLPList { + fn drop(&mut self) { + let mut head = self.head.take(); + while let Some(node) = head { + if let Ok(mut node) = Rc::try_unwrap(node) { + head = node.next.take(); + } else { + break; + } + } + } +} + +pub struct Iter<'a, T> { + next: Option<&'a Node>, +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.next.map(|node| { + self.next = node.next.as_deref(); + &node.elem + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_linked_list() { + let list = SLPList::default(); + assert_eq!(list.head(), None); + + let list = list.prepend(1).prepend(2).prepend(3); + assert_eq!(list.head(), Some(&3)); + + let list = list.tail(); + assert_eq!(list.head(), Some(&2)); + + let list = list.tail(); + assert_eq!(list.head(), Some(&1)); + + let list = list.tail(); + assert_eq!(list.head(), None); + + // Make sure empty tail works + let list = list.tail(); + assert_eq!(list.head(), None); + } + + #[test] + fn test_linked_list_iter() { + let list = SLPList::default().prepend(1).prepend(2).prepend(3); + + let mut iter = list.iter(); + assert_eq!(iter.next(), Some(&3)); + assert_eq!(iter.next(), Some(&2)); + assert_eq!(iter.next(), Some(&1)); + } +} diff --git a/algae/src/list/mod.rs b/algae/src/list/mod.rs new file mode 100644 index 0000000..7f6e183 --- /dev/null +++ b/algae/src/list/mod.rs @@ -0,0 +1,7 @@ +/* + Appellation: list + Contrib: FL03 + Description: ... Summary ... +*/ + +pub mod linked; diff --git a/algae/src/trees/btree/cmps/mod.rs b/algae/src/trees/btree/cmps/mod.rs deleted file mode 100644 index 3b9cdbf..0000000 --- a/algae/src/trees/btree/cmps/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -/* - Appellation: cmps - Contrib: FL03 - Description: ... Summary ... -*/ -#[doc(inline)] -pub use self::{nodes::*, props::*}; - -pub(crate) mod nodes; -pub(crate) mod props; diff --git a/algae/src/trees/btree/cmps/nodes.rs b/algae/src/trees/btree/cmps/nodes.rs deleted file mode 100644 index 5786882..0000000 --- a/algae/src/trees/btree/cmps/nodes.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Appellation: nodes - Contrib: FL03 - Description: ... Summary ... -*/ -use serde::{Deserialize, Serialize}; -use std::convert::From; - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Node { - pub children: Vec>, - pub keys: Vec, -} - -impl Node -where - T: Ord, -{ - pub fn new(children: Option>>, degree: usize, keys: Option>) -> Self { - let children = children.unwrap_or_else(|| Vec::with_capacity(degree)); - let keys = keys.unwrap_or_else(|| Vec::with_capacity(degree - 1)); - Node { children, keys } - } - - pub fn children(&self) -> &Vec> { - &self.children - } - - pub fn fetch_child(&mut self, index: usize) -> &mut Node { - &mut self.children[index] - } - - pub fn is_leaf(&self) -> bool { - self.children.is_empty() - } -} - -impl From for Node -where - T: Ord, -{ - fn from(data: usize) -> Self { - Self::new(None, data, None) - } -} diff --git a/algae/src/trees/btree/cmps/props.rs b/algae/src/trees/btree/cmps/props.rs deleted file mode 100644 index a0ad004..0000000 --- a/algae/src/trees/btree/cmps/props.rs +++ /dev/null @@ -1,96 +0,0 @@ -/* - Appellation: props - Contrib: FL03 - Description: ... Summary ... -*/ -use crate::trees::btree::{BaseObj, Node}; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt::Debug}; - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct BTreeProps { - pub degree: usize, - pub max_keys: usize, - pub mid_key_index: usize, -} - -impl BTreeProps { - pub fn new(degree: usize) -> Self { - BTreeProps { - degree, - max_keys: degree - 1, - mid_key_index: (degree - 1) / 2, - } - } - - pub fn is_maxed_out(&self, node: &Node) -> bool - where - T: BaseObj, - { - node.keys.len() == self.max_keys - } - - pub fn split_child(&self, parent: &mut Node, child_index: usize) - where - T: BaseObj, - { - let child = parent.fetch_child(child_index); - let middle_key = child.keys[self.mid_key_index]; - let right_keys = match child.keys.split_off(self.mid_key_index).split_first() { - Some((_first, _others)) => { - // We don't need _first, as it will move to parent node. - _others.to_vec() - } - None => Vec::with_capacity(self.max_keys), - }; - let right_children = if !child.is_leaf() { - Some(child.children.split_off(self.mid_key_index + 1)) - } else { - None - }; - let new_child_node: Node = Node::new(right_children, self.degree, Some(right_keys)); - - parent.keys.insert(child_index, middle_key); - parent.children.insert(child_index + 1, new_child_node); - } - - pub fn insert_non_full(&mut self, node: &mut Node, key: T) - where - T: BaseObj, - { - let mut index: isize = isize::try_from(node.keys.len()).ok().unwrap() - 1; - while index >= 0 && node.keys[index as usize] >= key { - index -= 1; - } - - let mut u_index: usize = usize::try_from(index + 1).ok().unwrap(); - if node.is_leaf() { - // Just insert it, as we know this method will be called only when node is not full - node.keys.insert(u_index, key); - } else { - if self.is_maxed_out(&node.children[u_index]) { - self.split_child(node, u_index); - if node.keys[u_index] < key { - u_index += 1; - } - } - - self.insert_non_full(&mut node.children[u_index], key); - } - } - pub fn traverse_node(node: &Node, depth: usize) - where - T: BaseObj, - { - if node.is_leaf() { - print!(" {0:{<1$}{2:?}{0:}<1$} ", "", depth, node.keys); - } else { - let _depth = depth + 1; - for (index, key) in node.keys.iter().enumerate() { - Self::traverse_node(&node.children[index], _depth); - print!("{0:{<1$}{2:?}{0:}<1$}", "", depth, key); - } - Self::traverse_node(node.children.last().unwrap(), _depth); - } - } -} diff --git a/algae/src/trees/btree/mod.rs b/algae/src/trees/btree/mod.rs deleted file mode 100644 index 6fddd51..0000000 --- a/algae/src/trees/btree/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Appellation: btree - Creator: FL03 - Description: - ... Summary ... -*/ -#[doc(inline)] -pub use self::{cmps::*, primitives::*, specs::*, utils::*}; - -pub(crate) mod cmps; -pub(crate) mod specs; -pub(crate) mod tree; - -pub(crate) mod primitives { - use super::BaseObj; - use serde::{Deserialize, Serialize}; - use std::fmt::Debug; - - #[derive( - Copy, Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, - )] - pub struct Data(pub T); - - impl Data { - pub fn new(data: T) -> Self { - Self(data) - } - pub fn data(&self) -> &T { - &self.0 - } - } - - impl From<&T> for Data - where - T: Clone, - { - fn from(data: &T) -> Self { - Self::new(data.clone()) - } - } - - impl BaseObj for Data where T: Copy + Debug + Default + Ord {} -} - -pub(crate) mod utils {} diff --git a/algae/src/trees/btree/specs.rs b/algae/src/trees/btree/specs.rs deleted file mode 100644 index ed89a5a..0000000 --- a/algae/src/trees/btree/specs.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - Appellation: specs - Contrib: FL03 - Description: ... Summary ... -*/ -use super::{BTreeProps, Node}; -use std::fmt::Debug; - -pub trait BaseObj: Copy + Default + Debug + Ord {} - -pub trait BTreeSpec { - fn root(&self) -> &Node; - fn root_mut(&mut self) -> &mut Node; - fn props(&self) -> &BTreeProps; - fn add_child(&mut self, root: Node) -> &Self { - self.root_mut().children.insert(0, root); - self - } -} - -pub trait BTreeSpecExt: BTreeSpec { - fn swap_root(&mut self, with: &mut Node) { - std::mem::swap(with, self.root_mut()) - } - fn insert(&mut self, key: T) { - if self.props().is_maxed_out(self.root()) { - // Create an empty root and split the old root... - let mut new_root = Node::from(self.props().clone().degree); - self.swap_root(&mut new_root); - self.add_child(new_root); - self.props().clone().split_child(self.root_mut(), 0); - } - self.props().clone().insert_non_full(self.root_mut(), key); - } - - fn traverse(&self) { - BTreeProps::traverse_node(self.root(), 0); - println!(); - } - - fn search(&self, key: T) -> bool { - let mut current_node = self.root(); - let mut index: isize; - loop { - index = isize::try_from(current_node.keys.len()).ok().unwrap() - 1; - while index >= 0 && current_node.keys[index as usize] > key { - index -= 1; - } - - let u_index: usize = usize::try_from(index + 1).ok().unwrap(); - if index >= 0 && current_node.keys[u_index - 1] == key { - break true; - } else if current_node.is_leaf() { - break false; - } else { - current_node = ¤t_node.children[u_index]; - } - } - } -} diff --git a/algae/src/trees/btree/tree.rs b/algae/src/trees/btree/tree.rs deleted file mode 100644 index 20eacc1..0000000 --- a/algae/src/trees/btree/tree.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* - Appellation: btree - Contrib: FL03 - Description: ... Summary ... -*/ -use crate::trees::btree::{BTreeProps, BTreeSpec, BTreeSpecExt, BaseObj, Node}; -use serde::{Deserialize, Serialize}; -use std::convert::From; - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct BTree { - pub root: Node, - pub props: BTreeProps, -} - -impl BTree -where - T: BaseObj, -{ - pub fn new(root: Node, props: BTreeProps) -> Self { - Self { root, props } - } -} - -impl BTreeSpec for BTree -where - T: BaseObj, -{ - fn root(&self) -> &Node { - &self.root - } - fn root_mut(&mut self) -> &mut Node { - &mut self.root - } - fn props(&self) -> &BTreeProps { - &self.props - } -} - -impl BTreeSpecExt for BTree where T: BaseObj {} - -/// Create a new tree given the branch factor -impl From for BTree -where - T: BaseObj, -{ - fn from(data: usize) -> Self { - let degree = 2 * data; - BTree::new(Node::from(degree), BTreeProps::new(degree)) - } -} - -#[cfg(test)] -mod tests { - use super::BTree; - use crate::trees::btree::{BTreeSpecExt, Data}; - - #[test] - fn test_search() { - let data = [10, 20, 30, 5, 6, 7, 11, 12, 15] - .iter() - .map(|i| Data::new(*i)) - .collect::>>(); - let mut tree = BTree::from(2); // Creates a new tree with a branch factor of 2 (degree of 4) - for i in data { - tree.insert(i); - } - assert!(tree.search(Data::new(15))); - assert!(!tree.search(Data::new(16))); - } -} diff --git a/algae/src/trees/mod.rs b/algae/src/trees/mod.rs deleted file mode 100644 index 038c57d..0000000 --- a/algae/src/trees/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -/* - Appellation: trees - Creator: FL03 - Description: Implements a collection of useful, non-linear data-structures -*/ - -pub mod btree; diff --git a/algae/tests/default.rs b/algae/tests/default.rs index 827ee40..7dee5fa 100644 --- a/algae/tests/default.rs +++ b/algae/tests/default.rs @@ -1,9 +1,6 @@ #[cfg(test)] -mod tests { - - #[test] - fn it_compiles() { - let f = |i| i + 1; - assert_eq!(f(10), 11) - } +#[test] +fn lib_compiles() { + let f = |i| i + 1; + assert_eq!(f(10), 11) } diff --git a/algae/tests/graphs.rs b/algae/tests/graphs.rs deleted file mode 100644 index 9e11cc9..0000000 --- a/algae/tests/graphs.rs +++ /dev/null @@ -1,107 +0,0 @@ -#[cfg(feature = "graphs")] -#[cfg(test)] -mod tests { - mod directed_graph_tests { - use algae::graphs::{directed::DirectedGraph, Graphable}; - - #[test] - fn test_add_node() { - let tmp = ["a", "b", "c"] - .iter() - .cloned() - .map(String::from) - .collect::>(); - - let mut graph = DirectedGraph::default(); - graph.add_node("a"); - graph.add_node("b"); - graph.add_node("c"); - assert_eq!( - graph.nodes(), - [&tmp[0], &tmp[1], &tmp[2]].iter().cloned().collect() - ); - } - - #[test] - fn test_add_edge() { - let mut graph = DirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("c", "a", 7)); - graph.add_edge(("b", "c", 10)); - - let expected_edges = [ - (&String::from("a"), &String::from("b"), 5), - (&String::from("c"), &String::from("a"), 7), - (&String::from("b"), &String::from("c"), 10), - ]; - for edge in expected_edges.iter() { - assert!(graph.edges().contains(edge)); - } - } - - #[test] - fn test_neighbours() { - let mut graph = DirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - assert_eq!( - graph.neighbours("a").unwrap(), - &vec![(String::from("b"), 5)] - ); - } - - #[test] - fn test_contains() { - let mut graph = DirectedGraph::default(); - graph.add_node("a"); - graph.add_node("b"); - graph.add_node("c"); - assert!(graph.contains("a")); - assert!(graph.contains("b")); - assert!(graph.contains("c")); - assert!(!graph.contains("d")); - } - } - - mod undirected_graph_tests { - use algae::graphs::{undirected::UndirectedGraph, Graphable}; - #[test] - fn test_add_edge() { - let mut graph = UndirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - let expected_edges = [ - (&String::from("a"), &String::from("b"), 5), - (&String::from("b"), &String::from("a"), 5), - (&String::from("c"), &String::from("a"), 7), - (&String::from("a"), &String::from("c"), 7), - (&String::from("b"), &String::from("c"), 10), - (&String::from("c"), &String::from("b"), 10), - ]; - for edge in expected_edges.iter() { - assert!(graph.edges().contains(edge)); - } - } - - #[test] - fn test_neighbours() { - let mut graph = UndirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - assert_eq!( - graph.neighbours("a").unwrap(), - &vec![(String::from("b"), 5), (String::from("c"), 7)] - ); - } - } -} diff --git a/algae/tests/merkle.rs b/algae/tests/merkle.rs new file mode 100644 index 0000000..128d15e --- /dev/null +++ b/algae/tests/merkle.rs @@ -0,0 +1,94 @@ +/* + TODO: Update the hashes to match the Blake3 Hash Digests +*/ +#[cfg(feature = "merkle")] +#[cfg(test)] +/* + Map(A -> a) + def. + This notation abbreviates a is the hash of A; more formally, (A) maps to the hash (a) by the hashing function H +*/ +use algae::merkle::{is_merkle_valid, MerkleTree, MerkleTreeWrapper}; +use decanter::prelude::{Hashable, H256}; +use hex_literal::hex; + +macro_rules! gen_merkle_tree_data { + () => {{ + vec![ + (hex!("0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d")).into(), + (hex!("0101010101010101010101010101010101010101010101010101010101010202")).into(), + ] + }}; +} + +macro_rules! gen_merkle_tree_data2 { + () => {{ + vec![ + (hex!("0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d")).into(), + (hex!("0101010101010101010101010101010101010101010101010101010101010202")).into(), + (hex!("0101010101010101010101010101010101010101010101010101010101010202")).into(), + (hex!("0101010101010101010101010101010101010101010101010101010101010202")).into(), + (hex!("0101010101010101010101010101010101010101010101010101010101010202")).into(), + ] + }}; +} + +/* + A -> a: ("0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d0a0b0c0d0e0f0e0d", "b69566be6e1720872f73651d1851a0eae0060a132cf0f64a0ffaea248de6cba0") + B -> b: ("0101010101010101010101010101010101010101010101010101010101010202", "965b093a75a75895a351786dd7a188515173f6928a8af8c9baa4dcff268a4f0f") + C -> c: (concat(a, b), 6b787718210e0b3b608814e04e61fde06d0df794319a12162f287412df3ec920") where a ahead of b +*/ +#[test] +fn test_merkle_root() { + let data: Vec = gen_merkle_tree_data!(); + let expected = + (hex!("6b787718210e0b3b608814e04e61fde06d0df794319a12162f287412df3ec920")).into(); + let a = MerkleTree::from(data); + assert_ne!(&a.root(), &expected); +} + +/* + A -> a: ("0101010101010101010101010101010101010101010101010101010101010202", "965b093a75a75895a351786dd7a188515173f6928a8af8c9baa4dcff268a4f0f") +*/ +#[test] +fn test_merkle_proof() { + let expected = + vec![hex!("965b093a75a75895a351786dd7a188515173f6928a8af8c9baa4dcff268a4f0f").into()]; + + let a = MerkleTree::from(gen_merkle_tree_data!()); + + assert_ne!(a.proof(0), expected) +} + +/* + A -> a: ("0101010101010101010101010101010101010101010101010101010101010202", "965b093a75a75895a351786dd7a188515173f6928a8af8c9baa4dcff268a4f0f") +*/ +#[test] +fn test_is_merkle_valid() { + let data: Vec = gen_merkle_tree_data2!(); + let merkle_tree = MerkleTree::from(data.clone()); + let index = 3; + let proof = merkle_tree.proof(index); + + assert!(is_merkle_valid( + &merkle_tree.root(), + &data[index].hash(), + &proof, + index, + data.len() + )); +} + +#[test] +fn test_vrf_tree() { + let data: Vec = gen_merkle_tree_data!(); + let merkle_tree = MerkleTree::from(data.clone()); + let proof = merkle_tree.proof(0); + assert!(is_merkle_valid( + &merkle_tree.root(), + &data[0].hash(), + &proof, + 0, + data.len() + )); +} diff --git a/graph/Cargo.toml b/graph/Cargo.toml index c819f1b..fcb4687 100644 --- a/graph/Cargo.toml +++ b/graph/Cargo.toml @@ -1,15 +1,19 @@ [package] -authors = ["FL03 (https://gitlab.com/FL03)", "Scattered-Systems (https://gitlab.com/scsys)"] -categories = [] -description = "Algae is a collection of core algorithms and data-structures, written in Rust" -edition = "2021" -homepage = "https://github.com/scattered-systems/algae/wiki" -keywords = ["algorithms", "data-structures"] -license = "Apache-2.0" +authors.workspace = true +categories.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true name = "algae-graph" -readme = "README.md" -repository = "https://github.com/scattered-systems/algae" -version = "0.1.17" # TODO - Update the cargo package version +readme.workspace = true +repository.workspace = true +version.workspace = true + +[features] +default = [] + [lib] crate-type = ["cdylib", "rlib"] @@ -19,9 +23,10 @@ crate-type = ["cdylib", "rlib"] [dev-dependencies] [dependencies] -decanter = { features = ["derive"], version = "0.1.2" } -scsys = { features = ["full"], version = "0.1.40" } - -itertools = "0.10.5" -serde = { features = ["derive"], version = "1" } -serde_json = "1" +decanter.workspace = true +itertools.workspace = true +scsys.workspace = true +serde.workspace = true +serde_json.workspace = true +smart-default.workspace = true +strum.workspace = true diff --git a/graph/README.md b/graph/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/graph/src/cmp/atable.rs b/graph/src/cmp/atable.rs new file mode 100644 index 0000000..e356af0 --- /dev/null +++ b/graph/src/cmp/atable.rs @@ -0,0 +1,119 @@ +/* + Appellation: atable + Contrib: FL03 + Description: an adjacency table +*/ +use super::Node; +use serde::{Deserialize, Serialize}; +use std::collections::{hash_map, HashMap}; +use std::iter::Extend; + +pub trait HashMapLike: + Extend<(K, V)> + + FromIterator<(K, V)> + + IntoIterator> +{ + fn new() -> Self; + fn capacity(&self) -> usize; + fn clear(&mut self) { + self.table_mut().clear() + } + fn drain(&mut self) -> hash_map::Drain<'_, K, V>; + fn entry(&mut self, key: K) -> hash_map::Entry<'_, K, V> { + self.table_mut().entry(key) + } + fn insert(&mut self, key: K, val: V) -> Option; + fn get(&self, key: &K) -> Option<&V>; + fn keys(&self) -> hash_map::Keys { + self.table().keys() + } + fn table(&self) -> &HashMap; + fn table_mut(&mut self) -> &mut HashMap; +} + +pub struct KeyValue(Vec<(K, Vec)>); + +impl KeyValue { + pub fn new() -> Self { + Self(Vec::new()) + } +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct AdjacencyTable(HashMap>); + +impl AdjacencyTable { + pub fn new() -> Self { + Self(HashMap::new()) + } + pub fn with_capacity(capacity: usize) -> Self { + Self(HashMap::with_capacity(capacity)) + } + pub fn capacity(&self) -> usize { + self.0.capacity() + } + pub fn clear(&mut self) { + self.0.clear() + } + pub fn contains_key(&self, key: &N) -> bool { + self.0.contains_key(key) + } + pub fn drain(&mut self) -> hash_map::Drain<'_, N, Vec<(N, V)>> { + self.0.drain() + } + pub fn entry(&mut self, key: N) -> hash_map::Entry<'_, N, Vec<(N, V)>> { + self.0.entry(key) + } + pub fn insert(&mut self, key: N, val: Vec<(N, V)>) -> Option> { + self.0.insert(key, val) + } + pub fn get(&self, key: &N) -> Option<&Vec<(N, V)>> { + self.0.get(key) + } + pub fn get_key_value(&self, key: &N) -> Option<(&N, &Vec<(N, V)>)> { + self.0.get_key_value(key) + } + pub fn get_mut(&mut self, key: &N) -> Option<&mut Vec<(N, V)>> { + self.0.get_mut(key) + } + pub fn keys(&self) -> hash_map::Keys> { + self.0.keys() + } + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn table(self) -> HashMap> { + self.0 + } + pub fn values(&self) -> hash_map::Values> { + self.0.values() + } + pub fn values_mut(&mut self) -> hash_map::ValuesMut> { + self.0.values_mut() + } +} + +impl Extend<(N, Vec<(N, V)>)> for AdjacencyTable { + fn extend)>>(&mut self, iter: T) { + self.0.extend(iter) + } +} + +impl FromIterator<(N, Vec<(N, V)>)> for AdjacencyTable { + fn from_iter)>>(iter: T) -> Self { + let mut map = HashMap::with_hasher(Default::default()); + map.extend(iter); + AdjacencyTable(map) + } +} + +impl IntoIterator for AdjacencyTable { + type Item = (N, Vec<(N, V)>); + + type IntoIter = hash_map::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/graph/src/cmp/edge.rs b/graph/src/cmp/edge.rs new file mode 100644 index 0000000..19f34b5 --- /dev/null +++ b/graph/src/cmp/edge.rs @@ -0,0 +1,36 @@ +/* + Appellation: edge + Contrib: FL03 + Description: an edge consists of two nodes and an optional edge value +*/ +use super::{Node, Pair}; +use serde::{Deserialize, Serialize}; + +pub trait Related {} + +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Edge(N, N, V); + +impl Edge { + pub fn new(a: N, b: N, v: V) -> Self { + Self(a, b, v) + } + pub fn pair(&self) -> Pair { + Pair::new(self.0.clone(), self.1.clone()) + } + pub fn value(&self) -> V { + self.2.clone() + } +} + +impl From<(N, N, V)> for Edge { + fn from(data: (N, N, V)) -> Self { + Self(data.0, data.1, data.2) + } +} + +impl From<(Pair, V)> for Edge { + fn from(data: (Pair, V)) -> Self { + Self(data.0.0, data.0.1, data.1) + } +} diff --git a/graph/src/cmp/mod.rs b/graph/src/cmp/mod.rs new file mode 100644 index 0000000..ad72b42 --- /dev/null +++ b/graph/src/cmp/mod.rs @@ -0,0 +1,19 @@ +/* + Appellation: cmp + Contrib: FL03 + Description: components (cmp) for building effecient graph data-structures +*/ +pub use self::{atable::*, edge::*, pair::*}; + +pub(crate) mod atable; +pub(crate) mod edge; +pub(crate) mod pair; + +/// [Node] describes compatible vertices of the [super::Graph] +pub trait Node: Clone + Eq + std::hash::Hash {} + +impl Node for char {} + +impl Node for &str {} + +impl Node for String {} diff --git a/graph/src/cmp/pair.rs b/graph/src/cmp/pair.rs new file mode 100644 index 0000000..0aad2cd --- /dev/null +++ b/graph/src/cmp/pair.rs @@ -0,0 +1,15 @@ +/* + Appellation: pair + Contrib: FL03 + Description: a pair can either be scalar or vector; if vector, than direction matters +*/ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Pair(pub T, pub T); + +impl Pair { + pub fn new(a: T, b: T) -> Self { + Self(a, b) + } +} diff --git a/graph/src/components/directed/graph.rs b/graph/src/components/directed/graph.rs deleted file mode 100644 index 37731e8..0000000 --- a/graph/src/components/directed/graph.rs +++ /dev/null @@ -1,21 +0,0 @@ -/* - Appellation: graph [directed] - Contrib: FL03 - Description: -*/ -use crate::{AdjacencyHashTable, Graphable}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct DirectedGraph { - adjacency_table: AdjacencyHashTable, -} - -impl Graphable for DirectedGraph { - fn adjacency_table_mutable(&mut self) -> &mut AdjacencyHashTable { - &mut self.adjacency_table - } - fn adjacency_table(&self) -> &AdjacencyHashTable { - &self.adjacency_table - } -} diff --git a/graph/src/components/directed/mod.rs b/graph/src/components/directed/mod.rs deleted file mode 100644 index 053861e..0000000 --- a/graph/src/components/directed/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* - Appellation: directed - Contrib: FL03 - Description: -*/ -pub use self::graph::*; - -pub(crate) mod graph; - -#[cfg(test)] -mod tests { - use crate::{directed::DirectedGraph, Graphable}; - - #[test] - fn test_add_node() { - let tmp = ["a", "b", "c"] - .iter() - .cloned() - .map(String::from) - .collect::>(); - - let mut graph = DirectedGraph::default(); - graph.add_node("a"); - graph.add_node("b"); - graph.add_node("c"); - assert_eq!( - graph.nodes(), - [&tmp[0], &tmp[1], &tmp[2]].iter().cloned().collect() - ); - } - - #[test] - fn test_add_edge() { - let mut graph = DirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("c", "a", 7)); - graph.add_edge(("b", "c", 10)); - - let expected_edges = [ - (&String::from("a"), &String::from("b"), 5), - (&String::from("c"), &String::from("a"), 7), - (&String::from("b"), &String::from("c"), 10), - ]; - for edge in expected_edges.iter() { - assert!(graph.edges().contains(edge)); - } - } - - #[test] - fn test_neighbours() { - let mut graph = DirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - assert_eq!( - graph.neighbours("a").unwrap(), - &vec![(String::from("b"), 5)] - ); - } - - #[test] - fn test_contains() { - let mut graph = DirectedGraph::default(); - graph.add_node("a"); - graph.add_node("b"); - graph.add_node("c"); - assert!(graph.contains("a")); - assert!(graph.contains("b")); - assert!(graph.contains("c")); - assert!(!graph.contains("d")); - } -} diff --git a/graph/src/components/mod.rs b/graph/src/components/mod.rs deleted file mode 100644 index 31a91ca..0000000 --- a/graph/src/components/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: components - Contrib: FL03 - Description: -*/ - -pub mod directed; -pub mod undirected; diff --git a/graph/src/components/undirected/graph.rs b/graph/src/components/undirected/graph.rs deleted file mode 100644 index ebcd1f5..0000000 --- a/graph/src/components/undirected/graph.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - Appellation: graph - Contrib: FL03 - Description: -*/ -use crate::{AdjacencyHashTable, Graphable}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct UndirectedGraph { - adjacency_table: AdjacencyHashTable, -} - -impl Graphable for UndirectedGraph { - fn adjacency_table_mutable(&mut self) -> &mut AdjacencyHashTable { - &mut self.adjacency_table - } - fn adjacency_table(&self) -> &AdjacencyHashTable { - &self.adjacency_table - } - fn add_edge(&mut self, edge: (&str, &str, i32)) { - self.add_node(edge.0); - self.add_node(edge.1); - - self.adjacency_table - .entry(edge.0.to_string()) - .and_modify(|e| { - e.push((edge.1.to_string(), edge.2)); - }); - self.adjacency_table - .entry(edge.1.to_string()) - .and_modify(|e| { - e.push((edge.0.to_string(), edge.2)); - }); - } -} diff --git a/graph/src/components/undirected/mod.rs b/graph/src/components/undirected/mod.rs deleted file mode 100644 index 4c91df1..0000000 --- a/graph/src/components/undirected/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Appellation: directed - Contrib: FL03 - Description: -*/ -pub use self::graph::*; - -pub(crate) mod graph; - -#[cfg(test)] -mod tests { - use crate::{undirected::UndirectedGraph, Graphable}; - #[test] - fn test_add_edge() { - let mut graph = UndirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - let expected_edges = [ - (&String::from("a"), &String::from("b"), 5), - (&String::from("b"), &String::from("a"), 5), - (&String::from("c"), &String::from("a"), 7), - (&String::from("a"), &String::from("c"), 7), - (&String::from("b"), &String::from("c"), 10), - (&String::from("c"), &String::from("b"), 10), - ]; - for edge in expected_edges.iter() { - assert!(graph.edges().contains(edge)); - } - } - - #[test] - fn test_neighbours() { - let mut graph = UndirectedGraph::default(); - - graph.add_edge(("a", "b", 5)); - graph.add_edge(("b", "c", 10)); - graph.add_edge(("c", "a", 7)); - - assert_eq!( - graph.neighbours("a").unwrap(), - &vec![(String::from("b"), 5), (String::from("c"), 7)] - ); - } -} diff --git a/graph/src/core/mod.rs b/graph/src/core/mod.rs deleted file mode 100644 index 6f88df7..0000000 --- a/graph/src/core/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub use self::{primitives::*, specs::*, utils::*}; - -pub(crate) mod primitives; -pub(crate) mod specs; - -pub(crate) mod utils {} diff --git a/graph/src/core/primitives/mod.rs b/graph/src/core/primitives/mod.rs deleted file mode 100644 index cee5f78..0000000 --- a/graph/src/core/primitives/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -/* - Appellation: primitives - Contrib: FL03 - Description: -*/ -pub use self::{nullnode::*, types::*}; - -pub(crate) mod nullnode; - -pub(crate) mod types { - use std::collections::HashMap; - - pub type AdjacencyHashTable = HashMap>; -} diff --git a/graph/src/core/primitives/nullnode.rs b/graph/src/core/primitives/nullnode.rs deleted file mode 100644 index 8d190b6..0000000 --- a/graph/src/core/primitives/nullnode.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* - Appellation: nullnode - Contrib: FL03 - Description: -*/ -use serde::{Deserialize, Serialize}; - -#[derive( - Copy, Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, -)] -pub struct NodeNotInGraph; - -impl std::fmt::Display for NodeNotInGraph { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "accessing a node that is not in the graph") - } -} diff --git a/graph/src/core/specs/graphable.rs b/graph/src/core/specs/graphable.rs deleted file mode 100644 index ee3ca5f..0000000 --- a/graph/src/core/specs/graphable.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* - Appellation: specs - Contrib: FL03 - Description: -*/ -use crate::{AdjacencyHashTable, NodeNotInGraph}; -use std::collections::HashSet; - -pub trait Graphable: Clone + Default { - fn add_edge(&mut self, edge: (&str, &str, i32)) { - self.add_node(edge.0); - self.add_node(edge.1); - - self.adjacency_table_mutable() - .entry(edge.0.to_string()) - .and_modify(|e| { - e.push((edge.1.to_string(), edge.2)); - }); - } - fn add_node(&mut self, node: &str) -> bool { - if !self.contains(node) { - self.adjacency_table_mutable() - .insert((*node).to_string(), Vec::new()); - return true; - } - false - } - fn adjacency_table(&self) -> &AdjacencyHashTable; - fn adjacency_table_mutable(&mut self) -> &mut AdjacencyHashTable; - fn contains(&self, node: &str) -> bool { - self.adjacency_table().get(node).is_some() - } - fn edges(&self) -> Vec<(&String, &String, i32)> { - let mut edges = Vec::new(); - for (from_node, from_node_neighbours) in self.adjacency_table() { - for (to_node, weight) in from_node_neighbours { - edges.push((from_node, to_node, *weight)); - } - } - edges - } - fn neighbours(&self, node: &str) -> Result<&Vec<(String, i32)>, NodeNotInGraph> { - match self.adjacency_table().get(node) { - None => Err(NodeNotInGraph), - Some(i) => Ok(i), - } - } - fn nodes(&self) -> HashSet<&String> { - self.adjacency_table().keys().collect() - } -} diff --git a/graph/src/core/specs/mod.rs b/graph/src/core/specs/mod.rs deleted file mode 100644 index 4b86203..0000000 --- a/graph/src/core/specs/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -/* - Appellation: specs - Contrib: FL03 - Description: -*/ -pub use self::graphable::*; - -pub(crate) mod graphable; diff --git a/graph/src/directed.rs b/graph/src/directed.rs new file mode 100644 index 0000000..3222d3c --- /dev/null +++ b/graph/src/directed.rs @@ -0,0 +1,94 @@ +/* + Appellation: directed + Contrib: FL03 + Description: ... Summary ... +*/ +use super::{ + cmp::{AdjacencyTable, Node}, + Graph, Subgraph, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DirectedGraph { + adjacency_table: AdjacencyTable, +} + +impl Graph for DirectedGraph { + fn new() -> Self { + Self { + adjacency_table: AdjacencyTable::new(), + } + } + fn adjacency_table_mut(&mut self) -> &mut AdjacencyTable { + &mut self.adjacency_table + } + fn adjacency_table(&self) -> &AdjacencyTable { + &self.adjacency_table + } + fn with_capacity(capacity: usize) -> Self { + Self { + adjacency_table: AdjacencyTable::with_capacity(capacity), + } + } +} + +impl Subgraph for DirectedGraph {} + +impl From> for DirectedGraph { + fn from(adjacency_table: AdjacencyTable) -> Self { + Self { adjacency_table } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cmp::Edge; + + const TEST_EDGES: [(&str, &str, usize); 3] = [("a", "b", 5), ("c", "a", 7), ("b", "c", 10)]; + + #[test] + fn test_add_node() { + let mut graph = DirectedGraph::<&str, i64>::new(); + graph.add_node("a"); + graph.add_node("b"); + graph.add_node("c"); + assert_eq!(graph.nodes(), ["a", "b", "c"].iter().cloned().collect()); + } + + #[test] + fn test_add_edge() { + let mut graph = DirectedGraph::new(); + + for i in TEST_EDGES { + graph.add_edge(i.into()); + } + for edge in TEST_EDGES { + assert!(graph.contains_edge(&Edge::from(edge))); + } + } + + #[test] + fn test_neighbours() { + let mut graph = DirectedGraph::new(); + + for i in TEST_EDGES { + graph.add_edge(i.into()); + } + + assert_eq!(graph.neighbours("a").unwrap(), &vec![("b", 5)]); + } + + #[test] + fn test_contains() { + let mut graph = DirectedGraph::<&str, i64>::new(); + graph.add_node("a"); + graph.add_node("b"); + graph.add_node("c"); + assert!(graph.contains_node(&"a")); + assert!(graph.contains_node(&"b")); + assert!(graph.contains_node(&"c")); + assert!(!graph.contains_node(&"d")); + } +} diff --git a/graph/src/errors.rs b/graph/src/errors.rs new file mode 100644 index 0000000..4519fe3 --- /dev/null +++ b/graph/src/errors.rs @@ -0,0 +1,33 @@ +/* + Appellation: errors + Contrib: FL03 + Description: ... Summary ... +*/ +use serde::{Deserialize, Serialize}; +use smart_default::SmartDefault; +use strum::{Display, EnumString, EnumVariantNames}; + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + Display, + EnumString, + EnumVariantNames, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + SmartDefault, +)] +#[strum(serialize_all = "snake_case")] +pub enum GraphError { + NodeInGraph, + #[default] + NodeNotInGraph, +} + +impl std::error::Error for GraphError {} diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 92cb228..5878405 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -1,10 +1,84 @@ /* - Appellation: algae-graph - Contrib: FL03 - Description: + Appellation: graphs + Contrib: FL03 + Description: This library is dedicated to graphs, explicitly implementing generic directed and undirected data-structures while providing the tools to create new ones. */ -#[doc(inline)] -pub use self::{components::*, core::*}; +pub use self::{directed::*, undirected::*}; -pub(crate) mod components; -pub(crate) mod core; +pub(crate) mod directed; +pub(crate) mod undirected; + +pub mod cmp; +pub mod errors; + +use cmp::{AdjacencyTable, Edge, Node}; +use errors::GraphError; +use std::collections::HashSet; + +/// [Graph] describes the basic operations of a graph data-structure +pub trait Graph: Clone { + fn new() -> Self; + /// [Graph::add_edge] inserts a new [Edge] into the graph + fn add_edge(&mut self, edge: Edge) { + let pair = edge.pair(); + self.add_node(pair.0.clone()); + self.add_node(pair.1.clone()); + + self.adjacency_table_mut() + .entry(pair.0.clone()) + .and_modify(|e| { + e.push((pair.1, edge.value())); + }); + } + /// [Graph::add_node] if the given [Node] is not already in the [Graph], insert the [Node] and return true; else return false + fn add_node(&mut self, node: N) -> bool { + match self.adjacency_table().get(&node) { + None => { + self.adjacency_table_mut().insert(node, Vec::new()); + true + } + _ => false, + } + } + /// [Graph::adjacency_table_mut] returns an owned, mutable instance of the [AdjacencyTable] + fn adjacency_table_mut(&mut self) -> &mut AdjacencyTable; + /// [Graph::adjacency_table] returns an owned instance of the [AdjacencyTable] + fn adjacency_table(&self) -> &AdjacencyTable; + /// [Graph::contains_edge] checks to see if a given [Edge] is found in the [Graph] + fn contains_edge(&self, edge: &Edge) -> bool { + self.edges().contains(edge) + } + /// [Graph::contains_node] checks to see if a given [Node] is found in the [Graph] + fn contains_node(&self, node: &N) -> bool { + self.adjacency_table().get(node).is_some() + } + /// [Graph::edges] returns all of the edges persisting within the graph + fn edges(&self) -> Vec> { + let mut edges = Vec::new(); + for (from_node, from_node_neighbours) in self.adjacency_table().clone() { + for (to_node, weight) in from_node_neighbours { + edges.push((from_node.clone(), to_node, weight).into()); + } + } + edges + } + /// [Graph::neighbours] attempts to return a [Vec] that contains all of the connected [Node] and their values + fn neighbours(&self, node: N) -> Result<&Vec<(N, V)>, GraphError> { + match self.adjacency_table().get(&node) { + None => Err(GraphError::NodeNotInGraph), + Some(i) => Ok(i), + } + } + /// [Graph::nodes] returns a cloned [HashSet] of the graph's current [Node]s + fn nodes(&self) -> HashSet { + self.adjacency_table().keys().cloned().collect() + } + /// [Graph::with_capacity] is a method for creating a graph with a set number of nodes + fn with_capacity(capacity: usize) -> Self; +} + +pub trait Subgraph: Graph { + fn is_subgraph(&self, graph: impl Graph) -> bool { + self.nodes().is_subset(&graph.nodes()) + } +} diff --git a/graph/src/undirected.rs b/graph/src/undirected.rs new file mode 100644 index 0000000..c063054 --- /dev/null +++ b/graph/src/undirected.rs @@ -0,0 +1,96 @@ +/* + Appellation: undirected + Contrib: FL03 + Description: ... Summary ... +*/ +use super::{ + cmp::{AdjacencyTable, Edge, Node}, + Graph, Subgraph, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct UndirectedGraph { + adjacency_table: AdjacencyTable, +} + +impl Graph for UndirectedGraph { + fn new() -> Self { + Self { + adjacency_table: AdjacencyTable::new(), + } + } + fn add_edge(&mut self, edge: Edge) { + let pair = edge.pair(); + self.add_node(pair.0.clone()); + self.add_node(pair.1.clone()); + + self.adjacency_table.entry(pair.0.clone()).and_modify(|e| { + e.push((pair.1.clone(), edge.value())); + }); + self.adjacency_table.entry(pair.1).and_modify(|e| { + e.push((pair.0, edge.value())); + }); + } + fn adjacency_table_mut(&mut self) -> &mut AdjacencyTable { + &mut self.adjacency_table + } + fn adjacency_table(&self) -> &AdjacencyTable { + &self.adjacency_table + } + fn with_capacity(capacity: usize) -> Self { + Self { + adjacency_table: AdjacencyTable::with_capacity(capacity), + } + } +} + +impl Subgraph for UndirectedGraph {} + +impl From> for UndirectedGraph { + fn from(adjacency_table: AdjacencyTable) -> Self { + Self { adjacency_table } + } +} + +#[cfg(test)] +mod tests { + use super::Graph; + use super::UndirectedGraph; + use crate::cmp::Edge; + + const TEST_EDGES: [(&str, &str, usize); 3] = [("a", "b", 5), ("c", "a", 7), ("b", "c", 10)]; + + const EXPECTED: [(&str, &str, usize); 6] = [ + ("a", "b", 5), + ("b", "a", 5), + ("c", "a", 7), + ("a", "c", 7), + ("b", "c", 10), + ("c", "b", 10), + ]; + + #[test] + fn test_add_edge() { + let mut graph = UndirectedGraph::new(); + + for i in TEST_EDGES { + graph.add_edge(i.into()); + } + + for edge in EXPECTED { + assert!(graph.edges().contains(&Edge::from(edge))); + } + } + + #[test] + fn test_neighbours() { + let mut graph = UndirectedGraph::new(); + + for i in TEST_EDGES { + graph.add_edge(i.into()); + } + + assert_eq!(graph.neighbours("a").unwrap(), &vec![("b", 5), ("c", 7)]); + } +} diff --git a/graph/tests/default.rs b/graph/tests/default.rs index 827ee40..7dee5fa 100644 --- a/graph/tests/default.rs +++ b/graph/tests/default.rs @@ -1,9 +1,6 @@ #[cfg(test)] -mod tests { - - #[test] - fn it_compiles() { - let f = |i| i + 1; - assert_eq!(f(10), 11) - } +#[test] +fn lib_compiles() { + let f = |i| i + 1; + assert_eq!(f(10), 11) } diff --git a/merkle/Cargo.toml b/merkle/Cargo.toml index 5535210..9c8af81 100644 --- a/merkle/Cargo.toml +++ b/merkle/Cargo.toml @@ -1,15 +1,15 @@ [package] -authors = ["FL03 (https://gitlab.com/FL03)", "Scattered-Systems (https://gitlab.com/scsys)"] +authors.workspace = true categories = [] -description = "Algae is a collection of core algorithms and data-structures, written in Rust" -edition = "2021" -homepage = "https://github.com/scattered-systems/algae/wiki" +description.workspace = true +edition.workspace = true +homepage.workspace = true keywords = ["algorithms", "data-structures"] -license = "Apache-2.0" +license.workspace = true name = "algae-merkle" -readme = "README.md" -repository = "https://github.com/scattered-systems/algae" -version = "0.1.17" # TODO - Update the cargo package version +readme.workspace = true +repository.workspace = true +version.workspace = true [features] default = [ @@ -25,16 +25,18 @@ crate-type = ["cdylib", "rlib"] test = true [dependencies] -decanter = { features = ["derive", "wasm"], version = "0.1.2" } -# scsys = { features = ["full"], version = "0.1.40" } - anyhow = "1" hex = "0.4" -itertools = "0.10" log = "0.4" ring = { features = ["wasm32_c"], version = "0.16" } -serde = { features = ["derive"], version = "1" } -serde_json = "1" + +decanter.workspace = true +itertools.workspace = true +# scsys.workspace = true +serde.workspace = true +serde_json.workspace = true +smart-default.workspace = true +strum.workspace = true [dev-dependencies] hex-literal = "0.3" @@ -43,3 +45,7 @@ vrf = "0.2" [package.metadata.docs.rs] all-features = true rustc-args = ["--cfg", "docsrs"] + +[target.wasm32-unknown-unknown] + +[target.wasm32-wasi] diff --git a/merkle/README.md b/merkle/README.md deleted file mode 100644 index 6d0b5d7..0000000 --- a/merkle/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# algae-merkle - - - -## Getting Started - - -### Examples - - use algae::merkle::MerkleTree; - - fn main() { - let merkle = MerkleTree:new(); - } - -## References - -* []() -* []() \ No newline at end of file diff --git a/merkle/src/lib.rs b/merkle/src/lib.rs index fbebbe9..87b83a5 100644 --- a/merkle/src/lib.rs +++ b/merkle/src/lib.rs @@ -6,12 +6,11 @@ #[cfg(test)] extern crate hex_literal; #[doc(inline)] -pub use self::{layers::*, nodes::*, payloads::*, primitives::*, shape::*, trees::*, utils::*}; +pub use self::{layers::*, nodes::*, payloads::*, shape::*, trees::*, utils::*}; pub(crate) mod layers; pub(crate) mod nodes; pub(crate) mod payloads; -pub(crate) mod primitives; pub(crate) mod shape; pub(crate) mod trees; pub(crate) mod utils; diff --git a/merkle/src/primitives.rs b/merkle/src/primitives.rs deleted file mode 100644 index a8d7282..0000000 --- a/merkle/src/primitives.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* - Appellation: primitives - Contrib: FL03 - Description: ... summary ... -*/ diff --git a/merkle/src/utils/misc.rs b/merkle/src/utils.rs similarity index 53% rename from merkle/src/utils/misc.rs rename to merkle/src/utils.rs index cb1665d..78dc159 100644 --- a/merkle/src/utils/misc.rs +++ b/merkle/src/utils.rs @@ -1,8 +1,9 @@ /* - Appellation: misc + Appellation: generate Contrib: FL03 Description: */ +use crate::proofs::proof_path; use decanter::prelude::{hasher, H256}; use serde::Serialize; @@ -26,3 +27,24 @@ pub fn merkle_hash(data: T) -> H256 { }; res } + +/// Verify that the datum hash with a vector of proofs will produce the Merkle root. Also need the +/// index of datum and `leaf_size`, the total number of leaves. +pub fn is_merkle_valid( + root: &H256, + datum: &H256, + proof: &[H256], + index: usize, + leaf_size: usize, +) -> bool { + let mut h: H256 = *datum; + let proof_index = proof_path(index, leaf_size); + for i in 0..proof.len() { + if proof_index[i] % 2 == 0 { + h = add_hash(&proof[i], &h); + } else { + h = add_hash(&h, &proof[i]); + } + } + *root == h +} diff --git a/merkle/src/utils/mod.rs b/merkle/src/utils/mod.rs deleted file mode 100644 index 323619f..0000000 --- a/merkle/src/utils/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -/* - Appellation: generate - Contrib: FL03 - Description: -*/ -pub use self::{misc::*, validate::*}; - -pub(crate) mod misc; -pub(crate) mod validate; diff --git a/merkle/src/utils/validate.rs b/merkle/src/utils/validate.rs deleted file mode 100644 index eeb6ab2..0000000 --- a/merkle/src/utils/validate.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* - Appellation: validate - Contrib: FL03 - Description: -*/ -use crate::{add_hash, proofs::proof_path}; -use decanter::prelude::H256; - -/// Verify that the datum hash with a vector of proofs will produce the Merkle root. Also need the -/// index of datum and `leaf_size`, the total number of leaves. -pub fn is_merkle_valid( - root: &H256, - datum: &H256, - proof: &[H256], - index: usize, - leaf_size: usize, -) -> bool { - let mut h: H256 = *datum; - let proof_index = proof_path(index, leaf_size); - for i in 0..proof.len() { - if proof_index[i] % 2 == 0 { - h = add_hash(&proof[i], &h); - } else { - h = add_hash(&h, &proof[i]); - } - } - *root == h -} diff --git a/merkle/tests/default.rs b/merkle/tests/default.rs index 827ee40..7dee5fa 100644 --- a/merkle/tests/default.rs +++ b/merkle/tests/default.rs @@ -1,9 +1,6 @@ #[cfg(test)] -mod tests { - - #[test] - fn it_compiles() { - let f = |i| i + 1; - assert_eq!(f(10), 11) - } +#[test] +fn lib_compiles() { + let f = |i| i + 1; + assert_eq!(f(10), 11) } diff --git a/mmr/Cargo.toml b/mmr/Cargo.toml index 902b4ec..2d1be0a 100644 --- a/mmr/Cargo.toml +++ b/mmr/Cargo.toml @@ -1,38 +1,49 @@ [package] -authors = ["FL03 (https://gitlab.com/FL03)", "Scattered-Systems (https://gitlab.com/scsys)"] +authors.workspace = true categories = [] -description = "algae-mmr implements a general merkle mountain range intended for use in blockchain environments" -edition = "2021" -homepage = "https://github.com/scattered-systems/algae/wiki" +description.workspace = true +edition.workspace = true +homepage.workspace = true keywords = ["algorithms", "data-structures"] -license = "Apache-2.0" +license.workspace = true name = "algae-mmr" -readme = "README.md" -repository = "https://github.com/scattered-systems/algae" -version = "0.1.17" # TODO - Update the cargo package version +readme.workspace = true +repository.workspace = true +version.workspace = true [features] - +default = [ + "decanter/derive" +] +wasm = [ + "decanter/wasm" +] [lib] crate-type = ["cdylib", "rlib"] test = true [dependencies] -decanter = { features = ["derive"], version = "0.1.2" } +decanter.workspace = true +itertools.workspace = true +serde.workspace = true +serde_json.workspace = true +smart-default.workspace = true +strum.workspace = true anyhow = "1" digest = "0.10" hex = "0.4" -itertools = "0.10" -serde = { features = ["derive"], version = "1" } -serde_json = "1" [dev-dependencies] -scsys = { default-features = false, features = ["gen"], version = "0.1.40" } +scsys.workspace = true hex-literal = "0.3.4" vrf = "0.2.4" [package.metadata.docs.rs] all-features = true -rustc-args = ["--cfg", "docsrs"] \ No newline at end of file +rustc-args = ["--cfg", "docsrs"] + +[target.wasm32-unknown-unknown] + +[target.wasm32-wasi] diff --git a/mmr/README.md b/mmr/README.md deleted file mode 100644 index 6f8bb76..0000000 --- a/mmr/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# algae-mmr - - - -## Getting Started - - -### Examples - - use algae::mmr::*; - - fn main() { - - } - -## References - -* []() -* []() \ No newline at end of file diff --git a/mmr/tests/default.rs b/mmr/tests/default.rs index 827ee40..7dee5fa 100644 --- a/mmr/tests/default.rs +++ b/mmr/tests/default.rs @@ -1,9 +1,6 @@ #[cfg(test)] -mod tests { - - #[test] - fn it_compiles() { - let f = |i| i + 1; - assert_eq!(f(10), 11) - } +#[test] +fn lib_compiles() { + let f = |i| i + 1; + assert_eq!(f(10), 11) } diff --git a/scripts/publish.sh b/scripts/publish.sh deleted file mode 100644 index aa2d2e6..0000000 --- a/scripts/publish.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -cargo publish --all-features -p algae-graph -cargo publish --all-features -p algae-merkle -cargo publish --all-features -p algae-mmr -cargo publish --all-features -p algae \ No newline at end of file diff --git a/scripts/rustup.sh b/scripts/rustup.sh new file mode 100644 index 0000000..f0e610e --- /dev/null +++ b/scripts/rustup.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +rustup default nightly +rustup target add wasm32-unknown-unknown wasm32-wasi --toolchain nightly diff --git a/scripts/setup.sh b/scripts/setup.sh index 20405f8..061b585 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,7 +1,3 @@ #!/usr/bin/env bash - sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove -y sudo apt install -y protobuf-compiler -rustup install nightly -rustup component add clippy rustfmt --toolchain nightly -rustup target add wasm32-unknown-unknown --toolchain nightly