diff --git a/Cargo.toml b/Cargo.toml index a81cec3..c5fdcba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ async-trait = "0.1.72" atoi = "2.0.0" atomic = "0.5.3" atomic_float = "0.1.0" +bitvec = "1.0.1" byte-slice-cast = "1.2.2" clap = { version = "4.3", features = ["derive"] } criterion = { version = "0.4.0", features = ["html_reports"] } diff --git a/crates/algos/Cargo.toml b/crates/algos/Cargo.toml index dc9f826..0f0ed26 100644 --- a/crates/algos/Cargo.toml +++ b/crates/algos/Cargo.toml @@ -14,6 +14,7 @@ license.workspace = true [dependencies] ahash.workspace = true atomic_float.workspace = true +bitvec.workspace = true clap = { workspace = true, optional = true } graph_builder = { path = "../builder", version = "^0.4.0" } log.workspace = true diff --git a/crates/algos/src/bfs.rs b/crates/algos/src/bfs.rs new file mode 100644 index 0000000..0fd2ab8 --- /dev/null +++ b/crates/algos/src/bfs.rs @@ -0,0 +1,250 @@ +use std::{collections::VecDeque, hash::Hash}; + +use bitvec::prelude::*; + +use crate::prelude::*; + +pub fn bfs_directed( + graph: &G, + node_ids: impl IntoIterator, + direction: Direction, +) -> DirectedBreadthFirst<'_, G, NI> +where + NI: Idx + Hash, + G: Graph + DirectedDegrees + DirectedNeighbors + Sync, +{ + DirectedBreadthFirst::new(graph, node_ids, direction) +} + +pub struct DirectedBreadthFirst<'a, G, NI> { + graph: &'a G, + seen: BitVec, + visited: BitVec, + queue: VecDeque, + direction: Direction, +} + +impl<'a, G, NI> DirectedBreadthFirst<'a, G, NI> +where + NI: Idx + Hash + std::fmt::Debug, + G: Graph + DirectedNeighbors + Sync, +{ + pub fn new(graph: &'a G, node_ids: impl IntoIterator, direction: Direction) -> Self { + let bitvec = BitVec::repeat(false, graph.node_count().index()); + let visited = bitvec.clone(); + + let mut seen = bitvec; + let mut queue = VecDeque::new(); + Self::enqueue_into(&mut seen, &mut queue, node_ids); + + Self { + graph, + seen, + visited, + queue, + direction, + } + } + + fn dequeue(&mut self) -> Option { + loop { + let node_id = self.queue.pop_front()?; + if !self.visited.replace(node_id.index(), true) { + return Some(node_id); + } + } + } + + fn enqueue_into( + seen: &mut BitVec, + queue: &mut VecDeque, + node_ids: impl IntoIterator, + ) { + for node_id in node_ids { + if !seen.replace(node_id.index(), true) { + queue.push_back(node_id); + } + } + } + + fn enqueue_out_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .out_neighbors(node_id) + .copied() + .filter(|node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.queue, node_ids); + } + + fn enqueue_in_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .in_neighbors(node_id) + .copied() + .filter(|node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.queue, node_ids); + } +} + +impl<'a, G, NI> Iterator for DirectedBreadthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + DirectedNeighbors + Sync, +{ + type Item = NI; + + fn next(&mut self) -> Option { + let node_id = self.dequeue()?; + + match self.direction { + Direction::Outgoing => self.enqueue_out_neighbors_of(node_id), + Direction::Incoming => self.enqueue_in_neighbors_of(node_id), + Direction::Undirected => { + self.enqueue_out_neighbors_of(node_id); + self.enqueue_in_neighbors_of(node_id); + } + } + + Some(node_id) + } +} + +pub fn bfs_undirected( + graph: &G, + node_ids: impl IntoIterator, +) -> UndirectedBreadthFirst<'_, G, NI> +where + NI: Idx + Hash, + G: Graph + UndirectedDegrees + UndirectedNeighbors + Sync, +{ + UndirectedBreadthFirst::new(graph, node_ids) +} + +pub struct UndirectedBreadthFirst<'a, G, NI> { + graph: &'a G, + seen: BitVec, + visited: BitVec, + queue: VecDeque, +} + +impl<'a, G, NI> UndirectedBreadthFirst<'a, G, NI> +where + NI: Idx + Hash + std::fmt::Debug, + G: Graph + UndirectedNeighbors + Sync, +{ + pub fn new(graph: &'a G, node_ids: impl IntoIterator) -> Self { + let bitvec = BitVec::repeat(false, graph.node_count().index()); + let visited = bitvec.clone(); + + let mut seen = bitvec; + let mut queue = VecDeque::new(); + Self::enqueue_into(&mut seen, &mut queue, node_ids); + + Self { + graph, + seen, + visited, + queue, + } + } + + fn dequeue(&mut self) -> Option { + loop { + let node_id = self.queue.pop_front()?; + + if !self.visited.replace(node_id.index(), true) { + return Some(node_id); + } + } + } + + fn enqueue_into( + seen: &mut BitVec, + queue: &mut VecDeque, + node_ids: impl IntoIterator, + ) { + for node_id in node_ids { + if !seen.replace(node_id.index(), true) { + queue.push_back(node_id); + } + } + } + + fn enqueue_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .neighbors(node_id) + .copied() + .filter(|&node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.queue, node_ids); + } +} + +impl<'a, G, NI> Iterator for UndirectedBreadthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + UndirectedNeighbors + Sync, +{ + type Item = NI; + + fn next(&mut self) -> Option { + let node_id = self.dequeue()?; + + self.enqueue_neighbors_of(node_id); + + Some(node_id) + } +} + +#[cfg(test)] +mod tests { + use graph::prelude::{CsrLayout, GraphBuilder}; + + use super::*; + + mod directed { + use super::*; + + #[test] + fn acyclic() { + let graph: DirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = bfs_directed(&graph, [0], Direction::Outgoing).collect(); + let expected: Vec = vec![0, 1, 2, 3]; + + assert_eq!(actual, expected); + } + + #[test] + fn cyclic() { + let graph: DirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 1), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = bfs_directed(&graph, [0], Direction::Outgoing).collect(); + let expected: Vec = vec![0, 1, 2, 3]; + + assert_eq!(actual, expected); + } + } + + #[test] + fn undirected() { + let graph: UndirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = bfs_undirected(&graph, [0]).collect(); + let expected: Vec = vec![0, 1, 2, 3]; + + assert_eq!(actual, expected); + } +} diff --git a/crates/algos/src/dfs.rs b/crates/algos/src/dfs.rs new file mode 100644 index 0000000..0cd2bac --- /dev/null +++ b/crates/algos/src/dfs.rs @@ -0,0 +1,250 @@ +use std::hash::Hash; + +use bitvec::prelude::*; + +use crate::prelude::*; + +pub fn dfs_directed( + graph: &G, + node_ids: impl IntoIterator, + direction: Direction, +) -> DirectedDepthFirst<'_, G, NI> +where + NI: Idx + Hash, + G: Graph + DirectedDegrees + DirectedNeighbors + Sync, +{ + DirectedDepthFirst::new(graph, node_ids, direction) +} + +pub struct DirectedDepthFirst<'a, G, NI> { + graph: &'a G, + seen: BitVec, + visited: BitVec, + stack: Vec, + direction: Direction, +} + +impl<'a, G, NI> DirectedDepthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + DirectedNeighbors + Sync, +{ + pub fn new(graph: &'a G, node_ids: impl IntoIterator, direction: Direction) -> Self { + let bitvec = BitVec::repeat(false, graph.node_count().index()); + let visited = bitvec.clone(); + + let mut seen = bitvec; + let mut stack = Vec::new(); + Self::enqueue_into(&mut seen, &mut stack, node_ids); + + Self { + graph, + seen, + visited, + stack, + direction, + } + } + + fn dequeue(&mut self) -> Option { + loop { + let node_id = self.stack.pop()?; + if !self.visited.replace(node_id.index(), true) { + return Some(node_id); + } + } + } + + fn enqueue_into( + seen: &mut BitVec, + stack: &mut Vec, + node_ids: impl IntoIterator, + ) { + for node_id in node_ids { + if !seen.replace(node_id.index(), true) { + stack.push(node_id); + } + } + } + + fn enqueue_out_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .out_neighbors(node_id) + .copied() + .filter(|node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.stack, node_ids); + } + + fn enqueue_in_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .in_neighbors(node_id) + .copied() + .filter(|node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.stack, node_ids); + } +} + +impl<'a, G, NI> Iterator for DirectedDepthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + DirectedNeighbors + Sync, +{ + type Item = NI; + + fn next(&mut self) -> Option { + let node_id = self.dequeue()?; + + match self.direction { + Direction::Outgoing => self.enqueue_out_neighbors_of(node_id), + Direction::Incoming => self.enqueue_in_neighbors_of(node_id), + Direction::Undirected => { + self.enqueue_out_neighbors_of(node_id); + self.enqueue_in_neighbors_of(node_id); + } + } + + Some(node_id) + } +} + +pub fn dfs_undirected( + graph: &G, + node_ids: impl IntoIterator, +) -> UndirectedDepthFirst<'_, G, NI> +where + NI: Idx + Hash, + G: Graph + UndirectedDegrees + UndirectedNeighbors + Sync, +{ + UndirectedDepthFirst::new(graph, node_ids) +} + +pub struct UndirectedDepthFirst<'a, G, NI> { + graph: &'a G, + seen: BitVec, + visited: BitVec, + stack: Vec, +} + +impl<'a, G, NI> UndirectedDepthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + UndirectedNeighbors + Sync, +{ + pub fn new(graph: &'a G, node_ids: impl IntoIterator) -> Self { + let bitvec = BitVec::repeat(false, graph.node_count().index()); + let visited = bitvec.clone(); + + let mut seen = bitvec; + let mut stack = Vec::new(); + Self::enqueue_into(&mut seen, &mut stack, node_ids); + + Self { + graph, + seen, + visited, + stack, + } + } + + fn dequeue(&mut self) -> Option { + loop { + let node_id = self.stack.pop()?; + + if !self.visited.replace(node_id.index(), true) { + return Some(node_id); + } + } + } + + fn enqueue_into( + seen: &mut BitVec, + stack: &mut Vec, + node_ids: impl IntoIterator, + ) { + for node_id in node_ids { + if !seen.replace(node_id.index(), true) { + stack.push(node_id); + } + } + } + + fn enqueue_neighbors_of(&mut self, node_id: NI) { + let node_ids = self + .graph + .neighbors(node_id) + .copied() + .filter(|&node_id| !self.visited[node_id.index()]); + + Self::enqueue_into(&mut self.seen, &mut self.stack, node_ids); + } +} + +impl<'a, G, NI> Iterator for UndirectedDepthFirst<'a, G, NI> +where + NI: Idx + Hash, + G: Graph + UndirectedNeighbors + Sync, +{ + type Item = NI; + + fn next(&mut self) -> Option { + let node_id = self.dequeue()?; + + self.enqueue_neighbors_of(node_id); + + Some(node_id) + } +} + +#[cfg(test)] +mod tests { + use graph::prelude::{CsrLayout, GraphBuilder}; + + use super::*; + + mod directed { + use super::*; + + #[test] + fn acyclic() { + let graph: DirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = dfs_directed(&graph, [0], Direction::Outgoing).collect(); + let expected: Vec = vec![0, 2, 3, 1]; + + assert_eq!(actual, expected); + } + + #[test] + fn cyclic() { + let graph: DirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 1), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = dfs_directed(&graph, [0], Direction::Outgoing).collect(); + let expected: Vec = vec![0, 2, 1, 3]; + + assert_eq!(actual, expected); + } + } + + #[test] + fn undirected() { + let graph: UndirectedCsrGraph = GraphBuilder::new() + .csr_layout(CsrLayout::Deduplicated) + .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 1), (3, 1)]) + .build(); + + let actual: Vec = dfs_undirected(&graph, [0]).collect(); + let expected: Vec = vec![0, 2, 3, 1]; + + assert_eq!(actual, expected); + } +} diff --git a/crates/algos/src/lib.rs b/crates/algos/src/lib.rs index 0dc74f7..55f4abd 100644 --- a/crates/algos/src/lib.rs +++ b/crates/algos/src/lib.rs @@ -141,6 +141,8 @@ //! ``` pub mod afforest; +pub mod bfs; +pub mod dfs; pub mod dss; pub mod page_rank; pub mod prelude;