From 54cc929de2e2348a6337189695d83b254bf9e763 Mon Sep 17 00:00:00 2001 From: andres Date: Thu, 17 Oct 2024 15:29:16 -0500 Subject: [PATCH] feat: Adds find_path_one_to_many --- DESCRIPTION | 2 +- NAMESPACE | 3 +++ R/extendr-wrappers.R | 4 ++++ R/find_path.R | 26 +++++++++++++++++++++++++ man/find_path_one_to_many.Rd | 24 +++++++++++++++++++++++ src/rust/src/lib.rs | 1 + src/rust/src/macros.rs | 10 ++++++++++ tests/testthat/test-find_path.R | 34 +++++++++++++++++++++++++++++++++ 8 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 man/find_path_one_to_many.Rd diff --git a/DESCRIPTION b/DESCRIPTION index e632936..7dd4a58 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,7 +23,7 @@ BugReports: https://github.com/ixpantia/orbweaver-r/issues License: MIT + file LICENSE Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Config/rextendr/version: 0.3.1.9000 SystemRequirements: Cargo (Rust's package manager) >= 1.70, rustc >= 1.70 Depends: diff --git a/NAMESPACE b/NAMESPACE index ecf105c..40edd76 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -15,6 +15,8 @@ S3method(find_all_paths,DirectedAcyclicGraph) S3method(find_all_paths,DirectedGraph) S3method(find_path,DirectedAcyclicGraph) S3method(find_path,DirectedGraph) +S3method(find_path_one_to_many,DirectedAcyclicGraph) +S3method(find_path_one_to_many,DirectedGraph) S3method(get_all_leaves,DirectedAcyclicGraph) S3method(get_all_leaves,DirectedGraph) S3method(get_all_roots,DirectedAcyclicGraph) @@ -47,6 +49,7 @@ export(build_directed) export(children) export(find_all_paths) export(find_path) +export(find_path_one_to_many) export(get_all_leaves) export(get_all_roots) export(get_leaves_under) diff --git a/R/extendr-wrappers.R b/R/extendr-wrappers.R index e0e380d..b361554 100644 --- a/R/extendr-wrappers.R +++ b/R/extendr-wrappers.R @@ -52,6 +52,8 @@ DirectedGraph$length <- function() .Call(wrap__DirectedGraph__length, self) DirectedGraph$find_all_paths <- function(from, to) .Call(wrap__DirectedGraph__find_all_paths, self, from, to) +DirectedGraph$find_path_one_to_many <- function(from, to) .Call(wrap__DirectedGraph__find_path_one_to_many, self, from, to) + #' @export `$.DirectedGraph` <- function (self, name) { func <- DirectedGraph[[name]]; environment(func) <- environment(); func } @@ -98,6 +100,8 @@ DirectedAcyclicGraph$length <- function() .Call(wrap__DirectedAcyclicGraph__leng DirectedAcyclicGraph$find_all_paths <- function(from, to) .Call(wrap__DirectedAcyclicGraph__find_all_paths, self, from, to) +DirectedAcyclicGraph$find_path_one_to_many <- function(from, to) .Call(wrap__DirectedAcyclicGraph__find_path_one_to_many, self, from, to) + #' @export `$.DirectedAcyclicGraph` <- function (self, name) { func <- DirectedAcyclicGraph[[name]]; environment(func) <- environment(); func } diff --git a/R/find_path.R b/R/find_path.R index 63b3b1c..cd19007 100644 --- a/R/find_path.R +++ b/R/find_path.R @@ -53,3 +53,29 @@ find_all_paths.DirectedGraph <- function(graph, from, to) { find_all_paths.DirectedAcyclicGraph <- function(graph, from, to) { graph$find_all_paths(from, to) } + +#' @title Find the shortest path from one node to many +#' +#' @description +#' Find the shortest path from one node to many +#' +#' Not all graphs support this function. Currently only +#' `DirectedAcyclicGraph` supports this. +#' @param graph A graph object +#' @param from The starting node of the path +#' @param to A character vector of nodes +#' @return A list of paths +#' @export +find_path_one_to_many <- function(graph, from, to) { + UseMethod("find_path_one_to_many") +} + +#' @export +find_path_one_to_many.DirectedGraph <- function(graph, from, to) { + graph$find_path_one_to_many(from, to) +} + +#' @export +find_path_one_to_many.DirectedAcyclicGraph <- function(graph, from, to) { + graph$find_path_one_to_many(from, to) +} diff --git a/man/find_path_one_to_many.Rd b/man/find_path_one_to_many.Rd new file mode 100644 index 0000000..c83e273 --- /dev/null +++ b/man/find_path_one_to_many.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/find_path.R +\name{find_path_one_to_many} +\alias{find_path_one_to_many} +\title{Find the shortest path from one node to many} +\usage{ +find_path_one_to_many(graph, from, to) +} +\arguments{ +\item{graph}{A graph object} + +\item{from}{The starting node of the path} + +\item{to}{A character vector of nodes} +} +\value{ +A list of paths +} +\description{ +Find the shortest path from one node to many + +Not all graphs support this function. Currently only +\code{DirectedAcyclicGraph} supports this. +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index dc30acd..55918d3 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -134,6 +134,7 @@ impl DirectedGraphBuilder { pub trait RImplDirectedGraph: Sized { fn find_path(&self, from: &str, to: &str) -> Result; + fn find_path_one_to_many(&self, from: &str, to: Strings) -> Result; fn children(&self, nodes: RNodesIn) -> Result; fn parents(&self, nodes: RNodesIn) -> Result; fn has_parents(&self, nodes: RNodesIn) -> Result>; diff --git a/src/rust/src/macros.rs b/src/rust/src/macros.rs index f8f396a..8a70c24 100644 --- a/src/rust/src/macros.rs +++ b/src/rust/src/macros.rs @@ -99,6 +99,16 @@ macro_rules! impl_directed_graph { .map(NodeVec) .collect()) } + + fn find_path_one_to_many(&self, from: &str, to: Strings) -> Result { + Ok(self + .0 + .find_path_one_to_many(from, to.iter()) + .map_err(to_r_error)? + .into_iter() + .map(NodeVec) + .collect()) + } } impl $ty { diff --git a/tests/testthat/test-find_path.R b/tests/testthat/test-find_path.R index cfe48e0..1026887 100644 --- a/tests/testthat/test-find_path.R +++ b/tests/testthat/test-find_path.R @@ -22,3 +22,37 @@ test_that("find all paths on directed acyclic graph", { list(c("A", "Z", "C"), c("A", "B", "C")) ) }) + +test_that("find path from one node to many nodes", { + + edges <- tibble::tibble( + Parent = c("A", "A", "B", "C", "D", "Z"), + Child = c("B", "C", "Z", "D", "Z", "F") + ) + + graph <- graph_builder() |> + populate_edges(edges, Parent, Child) |> + build_acyclic() + + edges$path_ow <- find_path_one_to_many(graph, "A", edges$Child) + edges$level <- sapply(edges$path_ow, length) + edges$path <- sapply(edges$path_ow, \(path) paste(as.character(path), collapse = "|")) + + expect_equal( + edges$level, + c(2, 2, 5, 3, 5, 6) + ) + + expect_equal( + edges$path, + c( + "A|B", + "A|C", + "A|B|C|D|Z", + "A|C|D", + "A|B|C|D|Z", + "A|B|C|D|Z|F" + ) + ) + +})