Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/zrx-graph/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl<T> Graph<T> {

#[allow(clippy::must_use_candidate)]
impl<T> Graph<T> {
/// Returns the graph topology.
/// Returns a reference to the graph topology.
#[inline]
pub fn topology(&self) -> &Topology {
&self.topology
Expand Down
2 changes: 1 addition & 1 deletion crates/zrx-graph/src/graph/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@

//! Graph operators.

mod adjacent;
mod map;
mod with;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

// ----------------------------------------------------------------------------

//! With operator.
//! Adjacent operator.

use crate::graph::Graph;

Expand All @@ -44,7 +44,7 @@ pub struct Adjacent<'a> {
// ----------------------------------------------------------------------------

impl<T> Graph<T> {
/// Retrieve a reference to a node's data.
/// Retrieve a reference to a node and its adjacent nodes.
///
/// # Examples
///
Expand All @@ -63,24 +63,20 @@ impl<T> Graph<T> {
/// builder.add_edge(a, b)?;
/// builder.add_edge(b, c)?;
///
/// // Create graph from builder and retrieve nodes
/// // Create graph from builder and retrieve node
/// let graph = builder.build();
/// for node in &graph {
/// graph.with(node, |name, _| {
/// println!("{name:?}");
/// });
/// }
///
/// // Obtain reference to node and adjacent nodes
/// let (data, adj) = graph.adjacent(a);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn with<F, R>(&self, node: usize, f: F) -> R
where
F: FnOnce(&T, Adjacent) -> R,
{
#[must_use]
pub fn adjacent(&self, node: usize) -> (&'_ T, Adjacent<'_>) {
let incoming = self.topology.incoming();
let outgoing = self.topology.outgoing();
f(
(
&self.data[node],
Adjacent {
incoming: &incoming[node],
Expand All @@ -89,7 +85,7 @@ impl<T> Graph<T> {
)
}

/// Retrieve a mutable reference to a node's data.
/// Retrieve a mutable reference to a node and its adjacent nodes.
///
/// # Examples
///
Expand All @@ -110,22 +106,18 @@ impl<T> Graph<T> {
///
/// // Create graph from builder and retrieve node
/// let mut graph = builder.build();
/// for node in &graph {
/// graph.with_mut(node, |name, _| {
/// println!("{name:?}");
/// });
/// }
///
/// // Obtain mutable reference to node and adjacent nodes
/// let (data, adj) = graph.adjacent_mut(a);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn with_mut<F, R>(&mut self, node: usize, f: F) -> R
where
F: FnOnce(&mut T, Adjacent) -> R,
{
#[must_use]
pub fn adjacent_mut(&mut self, node: usize) -> (&'_ mut T, Adjacent<'_>) {
let incoming = self.topology.incoming();
let outgoing = self.topology.outgoing();
f(
(
&mut self.data[node],
Adjacent {
incoming: &incoming[node],
Expand Down
45 changes: 32 additions & 13 deletions crates/zrx-graph/src/graph/traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,25 @@ impl Traversal {
Ok(())
}

/// Attempts to converge this traversal with another traversal.
/// Attempts to converge with the given traversal.
///
/// This method attempts to combine both traversals into a single traversal,
/// which is possible when they have common descendants. This is useful for
/// deduplication of computations that need to be carried out for traversals
/// that originate from different sources, but converge at some point.
/// This method attempts to merge both traversals into a single traversal,
/// which is possible when they have common descendants, a condition that
/// is always true for directed acyclic graphs with a single component.
/// There are several cases to consider when converging two traversals:
///
/// - If traversals start from the same set of source nodes, they already
/// converged, so we just restart the traversal at these source nodes.
///
/// - If traversals start from different source nodes, yet both have common
/// descendants, we converge at the first layer of common descendants, as
/// all descendants of them must be revisited in the combined traversal.
/// Ancestors of the common descendants that have already been visited in
/// either traversal don't need to be revisited, and thus are carried over
/// from both traversals in their current state.
///
/// - If traversals are disjoint, they can't be converged, so we return the
/// given traversal back to the caller wrapped in [`Error::Disjoint`].
///
/// # Errors
///
Expand Down Expand Up @@ -327,15 +340,15 @@ impl Traversal {
topology: self.topology.clone(),
};

// If there are no common descendants, the traversals are disjoint, and
// we can't converge them, so we return the traversal to the caller
// If there are no common descendants, the traversals are disjoint and
// can't converge, so we return the given traversal back to the caller
let mut iter = graph.common_descendants(&initial);
let Some(common) = iter.next() else {
return Err(Error::Disjoint(other));
};

// Create the combined traversal, and mark all already visited nodes
// that are ancestors of the common descendants as visited as well
// that are ancestors of the common descendants as visited
let prior = mem::replace(self, Self::new(&self.topology, initial));

// Compute the visitable nodes for the combined traversal, which is the
Expand All @@ -347,10 +360,10 @@ impl Traversal {
let p = prior.dependencies[node];
let o = other.dependencies[node];

// If the node has been visited in either traversal, and is not
// part of the common descendants, mark it as visited as well in
// the combined traversal, since we don't need to revisit nodes
// that are ancestors of the common descendants
// If the node has been visited in either traversal, and is not part
// of the first layer of common descendants, mark it as visited in
// the combined traversal, since we don't need to revisit ancestors
// that have already been visited in either traversal
if (p == u8::MAX || o == u8::MAX) && !common.contains(&node) {
self.complete(node)?;
} else {
Expand All @@ -368,12 +381,18 @@ impl Traversal {

#[allow(clippy::must_use_candidate)]
impl Traversal {
/// Returns the graph topology.
/// Returns a reference to the graph topology.
#[inline]
pub fn topology(&self) -> &Topology {
&self.topology
}

/// Returns a reference to the initial nodes.
#[inline]
pub fn initial(&self) -> &[usize] {
&self.initial
}

/// Returns the number of visitable nodes.
#[inline]
pub fn len(&self) -> usize {
Expand Down
12 changes: 4 additions & 8 deletions crates/zrx-id/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@ impl PartialEq for Id {
/// ```
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
// We first compare the precomputed hashes, which is extremly fast, as
// it saves us the comparison when the identifiers are different
self.hash == other.hash && self.format == other.format
}
}

Expand Down Expand Up @@ -417,13 +419,7 @@ impl Ord for Id {
/// ```
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
// Fast path - first, we compare for equality by using the precomputed
// hashes, as it's a simple and extremely fast integer comparison
if self.eq(other) {
Ordering::Equal
} else {
self.format.cmp(&other.format)
}
self.format.cmp(&other.format)
}
}

Expand Down
55 changes: 53 additions & 2 deletions crates/zrx-id/src/id/matcher/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

use ahash::AHasher;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::str::FromStr;
Expand Down Expand Up @@ -104,7 +105,7 @@ pub use convert::TryToSelector;
/// # Ok(())
/// # }
/// ```
#[derive(Clone, PartialOrd, Ord)]
#[derive(Clone)]
pub struct Selector {
/// Formatted string.
format: Arc<Format<7>>,
Expand Down Expand Up @@ -343,14 +344,64 @@ impl PartialEq for Selector {
/// ```
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
// We first compare the precomputed hashes, which is extremely fast, as
// it saves us the comparison when the identifiers are different
self.hash == other.hash && self.format == other.format
}
}

impl Eq for Selector {}

// ----------------------------------------------------------------------------

impl PartialOrd for Selector {
/// Orders two selectors.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use zrx_id::Selector;
///
/// // Create and compare selectors
/// let a: Selector = "zrs:::::**/*.md:".parse()?;
/// let b: Selector = "zrs:::::**/*.rs:".parse()?;
/// assert!(a < b);
/// # Ok(())
/// # }
/// ```
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Selector {
/// Orders two selectors.
///
/// # Examples
///
/// ```
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// use zrx_id::Selector;
///
/// // Create and compare selectors
/// let a: Selector = "zrs:::::**/*.md:".parse()?;
/// let b: Selector = "zrs:::::**/*.rs:".parse()?;
/// assert!(a < b);
/// # Ok(())
/// # }
/// ```
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.format.cmp(&other.format)
}
}

// ----------------------------------------------------------------------------

impl Display for Selector {
/// Formats the selector for display.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Expand Down