From abc9ac41d9d5588f31f0cc2753bffdb33d24c71a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:10:40 +0000 Subject: [PATCH 1/4] Initial plan From d86d927ad94ea685340b7f154122b35085acc712 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:18:45 +0000 Subject: [PATCH 2/4] feat: replace rustc-hash with ahash for better performance Co-authored-by: samueltardieu <44656+samueltardieu@users.noreply.github.com> --- Cargo.lock | 21 ++++++++++++++------- Cargo.toml | 2 +- src/directed/count_paths.rs | 6 +++--- src/directed/dfs.rs | 8 ++++---- src/directed/dijkstra.rs | 10 +++++----- src/lib.rs | 7 +++---- src/noderefs.rs | 14 +++++++------- src/undirected/connected_components.rs | 6 +++--- 8 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd893650..62da730c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,19 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -533,6 +546,7 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" name = "pathfinding" version = "4.14.0" dependencies = [ + "ahash", "codspeed-criterion-compat", "deprecate-until", "iai-callgrind", @@ -545,7 +559,6 @@ dependencies = [ "rand", "rand_xorshift", "regex", - "rustc-hash", "thiserror", "trybuild", "version_check", @@ -721,12 +734,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 9ca98786..d5b839da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ iai = [] [dependencies] num-traits = "0.2.19" indexmap = "2.11.4" -rustc-hash = "2.1.1" +ahash = "0.8.12" integer-sqrt = "0.1.5" thiserror = "2.0.17" deprecate-until = "0.1.1" diff --git a/src/directed/count_paths.rs b/src/directed/count_paths.rs index 29b4533f..3a3a949a 100644 --- a/src/directed/count_paths.rs +++ b/src/directed/count_paths.rs @@ -2,13 +2,13 @@ use std::hash::Hash; -use rustc_hash::FxHashMap; +use ahash::AHashMap; fn cached_count_paths( start: T, successors: &mut FN, success: &mut FS, - cache: &mut FxHashMap, + cache: &mut AHashMap, ) -> usize where T: Eq + Hash, @@ -66,6 +66,6 @@ where start, &mut successors, &mut success, - &mut FxHashMap::default(), + &mut AHashMap::default(), ) } diff --git a/src/directed/dfs.rs b/src/directed/dfs.rs index 15049889..5f0d97ac 100644 --- a/src/directed/dfs.rs +++ b/src/directed/dfs.rs @@ -5,7 +5,7 @@ use std::collections::HashSet; use std::hash::Hash; use std::iter::FusedIterator; -use rustc_hash::{FxHashMap, FxHashSet}; +use ahash::{AHashMap, AHashSet}; /// Compute a path using the [depth-first search /// algorithm](https://en.wikipedia.org/wiki/Depth-first_search). @@ -54,8 +54,8 @@ where FS: FnMut(&N) -> bool, { let mut to_visit = vec![start]; - let mut visited = FxHashSet::default(); - let mut parents = FxHashMap::default(); + let mut visited = AHashSet::default(); + let mut parents = AHashMap::default(); while let Some(node) = to_visit.pop() { if visited.insert(node.clone()) { if success(&node) { @@ -77,7 +77,7 @@ where None } -fn build_path(mut node: N, parents: &FxHashMap) -> Vec +fn build_path(mut node: N, parents: &AHashMap) -> Vec where N: Clone + Eq + Hash, { diff --git a/src/directed/dijkstra.rs b/src/directed/dijkstra.rs index 86958ebe..04e254c1 100644 --- a/src/directed/dijkstra.rs +++ b/src/directed/dijkstra.rs @@ -3,9 +3,9 @@ use super::reverse_path; use crate::FxIndexMap; +use ahash::{AHashMap, AHashSet}; use indexmap::map::Entry::{Occupied, Vacant}; use num_traits::Zero; -use rustc_hash::{FxHashMap, FxHashSet}; use std::cmp::Ordering; use std::collections::{BinaryHeap, HashMap}; use std::hash::Hash; @@ -326,9 +326,9 @@ impl Ord for SmallestHolder { /// Struct returned by [`dijkstra_reach`]. pub struct DijkstraReachable { to_see: BinaryHeap>, - seen: FxHashSet, + seen: AHashSet, parents: FxIndexMap, - total_costs: FxHashMap, + total_costs: AHashMap, successors: FN, } @@ -422,10 +422,10 @@ where let mut parents: FxIndexMap = FxIndexMap::default(); parents.insert(start.clone(), (usize::MAX, Zero::zero())); - let mut total_costs = FxHashMap::default(); + let mut total_costs = AHashMap::default(); total_costs.insert(start.clone(), Zero::zero()); - let seen = FxHashSet::default(); + let seen = AHashSet::default(); DijkstraReachable { to_see, diff --git a/src/lib.rs b/src/lib.rs index cfd162f1..26812ac7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,12 +122,11 @@ pub mod utils; mod noderefs; pub use noderefs::NodeRefs; +use ahash::RandomState; use indexmap::{IndexMap, IndexSet}; -use rustc_hash::FxHasher; -use std::hash::BuildHasherDefault; -type FxIndexMap = IndexMap>; -type FxIndexSet = IndexSet>; +type FxIndexMap = IndexMap; +type FxIndexSet = IndexSet; /// Export all public functions and structures for an easy access. pub mod prelude { diff --git a/src/noderefs.rs b/src/noderefs.rs index b11c7019..7b830f74 100644 --- a/src/noderefs.rs +++ b/src/noderefs.rs @@ -1,4 +1,4 @@ -use rustc_hash::FxHashSet; +use ahash::AHashSet; use std::hash::Hash; use std::iter::FromIterator; use std::ops::Deref; @@ -22,19 +22,19 @@ use std::ops::Deref; /// let refs: NodeRefs = NodeRefs::from_iter([&red, &blue, &green]); /// ``` #[derive(Debug, Clone, PartialEq, Eq)] -pub struct NodeRefs<'a, N>(FxHashSet<&'a N>) +pub struct NodeRefs<'a, N>(AHashSet<&'a N>) where N: Eq + Hash + Clone; impl<'a, N: Eq + Hash + Clone> FromIterator<&'a N> for NodeRefs<'a, N> { fn from_iter>(iter: T) -> Self { - NodeRefs(FxHashSet::from_iter(iter)) + NodeRefs(AHashSet::from_iter(iter)) } } impl<'a, N: Eq + Hash + Clone> From<&'a N> for NodeRefs<'a, N> { fn from(value: &'a N) -> Self { - NodeRefs(FxHashSet::from_iter([value])) + NodeRefs(AHashSet::from_iter([value])) } } @@ -57,7 +57,7 @@ impl<'a, N: Eq + Hash + Clone> IntoIterator for &'a NodeRefs<'a, N> { } impl<'a, N: Eq + Hash + Clone> Deref for NodeRefs<'a, N> { - type Target = FxHashSet<&'a N>; + type Target = AHashSet<&'a N>; fn deref(&self) -> &Self::Target { &self.0 @@ -78,7 +78,7 @@ mod tests { let refs = NodeRefs::from_iter(&nodes); assert_eq!( refs.0, - FxHashSet::from_iter([&nodes[0], &nodes[1], &nodes[2]]) + AHashSet::from_iter([&nodes[0], &nodes[1], &nodes[2]]) ); } @@ -86,7 +86,7 @@ mod tests { fn test_from_single_ref() { let node = Node(42); let refs = NodeRefs::from(&node); - assert_eq!(refs.0, FxHashSet::from_iter([&node])); + assert_eq!(refs.0, AHashSet::from_iter([&node])); } #[test] diff --git a/src/undirected/connected_components.rs b/src/undirected/connected_components.rs index 1f543d37..7eae155c 100644 --- a/src/undirected/connected_components.rs +++ b/src/undirected/connected_components.rs @@ -5,7 +5,7 @@ use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::marker::PhantomData; -use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; +use ahash::{AHashMap, AHashSet, RandomState}; /// A connected component implementation for various generic types. /// @@ -107,8 +107,8 @@ where let (_, gindices) = Self::separate_components(groups); // Pre-size the hash map to reduce reallocations let estimated_capacity = gindices.iter().filter(|&&n| n != usize::MAX).count(); - let mut gb: FxHashMap> = - FxHashMap::with_capacity_and_hasher(estimated_capacity, FxBuildHasher); + let mut gb: AHashMap> = + AHashMap::with_capacity_and_hasher(estimated_capacity, RandomState::new()); for (i, n) in gindices .into_iter() .enumerate() From 851b5ff6e7d91a688c412ebaf1b3ac0400a58916 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:35:03 +0000 Subject: [PATCH 3/4] fix: use deterministic ahash hasher to prevent test failures Replace RandomState with BuildHasherDefault to ensure deterministic hash map iteration order. This fixes the failing topological_sort doctest that was affected by non-deterministic hash ordering. Co-authored-by: samueltardieu <44656+samueltardieu@users.noreply.github.com> --- src/directed/count_paths.rs | 7 +++++-- src/directed/dfs.rs | 9 ++++++--- src/directed/dijkstra.rs | 10 +++++++--- src/lib.rs | 7 ++++--- src/noderefs.rs | 8 ++++++-- src/undirected/connected_components.rs | 8 ++++---- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/directed/count_paths.rs b/src/directed/count_paths.rs index 3a3a949a..6c77d2f0 100644 --- a/src/directed/count_paths.rs +++ b/src/directed/count_paths.rs @@ -1,8 +1,11 @@ //! Count the total number of possible paths to reach a destination. -use std::hash::Hash; +use std::collections::HashMap; +use std::hash::{BuildHasherDefault, Hash}; -use ahash::AHashMap; +use ahash::AHasher; + +type AHashMap = HashMap>; fn cached_count_paths( start: T, diff --git a/src/directed/dfs.rs b/src/directed/dfs.rs index 5f0d97ac..ea0fa0cb 100644 --- a/src/directed/dfs.rs +++ b/src/directed/dfs.rs @@ -1,11 +1,14 @@ //! Compute a path using the [depth-first search //! algorithm](https://en.wikipedia.org/wiki/Depth-first_search). -use std::collections::HashSet; -use std::hash::Hash; +use std::collections::{HashMap, HashSet}; +use std::hash::{BuildHasherDefault, Hash}; use std::iter::FusedIterator; -use ahash::{AHashMap, AHashSet}; +use ahash::AHasher; + +type AHashMap = HashMap>; +type AHashSet = HashSet>; /// Compute a path using the [depth-first search /// algorithm](https://en.wikipedia.org/wiki/Depth-first_search). diff --git a/src/directed/dijkstra.rs b/src/directed/dijkstra.rs index 04e254c1..ceee8a42 100644 --- a/src/directed/dijkstra.rs +++ b/src/directed/dijkstra.rs @@ -3,12 +3,16 @@ use super::reverse_path; use crate::FxIndexMap; -use ahash::{AHashMap, AHashSet}; use indexmap::map::Entry::{Occupied, Vacant}; use num_traits::Zero; use std::cmp::Ordering; -use std::collections::{BinaryHeap, HashMap}; -use std::hash::Hash; +use std::collections::{BinaryHeap, HashMap, HashSet}; +use std::hash::{BuildHasherDefault, Hash}; + +use ahash::AHasher; + +type AHashMap = HashMap>; +type AHashSet = HashSet>; /// Compute a shortest path using the [Dijkstra search /// algorithm](https://en.wikipedia.org/wiki/Dijkstra's_algorithm). diff --git a/src/lib.rs b/src/lib.rs index 26812ac7..692604ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,11 +122,12 @@ pub mod utils; mod noderefs; pub use noderefs::NodeRefs; -use ahash::RandomState; +use ahash::AHasher; use indexmap::{IndexMap, IndexSet}; +use std::hash::BuildHasherDefault; -type FxIndexMap = IndexMap; -type FxIndexSet = IndexSet; +type FxIndexMap = IndexMap>; +type FxIndexSet = IndexSet>; /// Export all public functions and structures for an easy access. pub mod prelude { diff --git a/src/noderefs.rs b/src/noderefs.rs index 7b830f74..6fa4b4fb 100644 --- a/src/noderefs.rs +++ b/src/noderefs.rs @@ -1,8 +1,12 @@ -use ahash::AHashSet; -use std::hash::Hash; +use std::collections::HashSet; +use std::hash::{BuildHasherDefault, Hash}; use std::iter::FromIterator; use std::ops::Deref; +use ahash::AHasher; + +type AHashSet = HashSet>; + /// A set of node references. /// /// Can be created from a single node reference or an iterable of diff --git a/src/undirected/connected_components.rs b/src/undirected/connected_components.rs index 7eae155c..1e695d9b 100644 --- a/src/undirected/connected_components.rs +++ b/src/undirected/connected_components.rs @@ -2,10 +2,10 @@ use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; -use std::hash::Hash; +use std::hash::{BuildHasherDefault, Hash}; use std::marker::PhantomData; -use ahash::{AHashMap, AHashSet, RandomState}; +use ahash::AHasher; /// A connected component implementation for various generic types. /// @@ -107,8 +107,8 @@ where let (_, gindices) = Self::separate_components(groups); // Pre-size the hash map to reduce reallocations let estimated_capacity = gindices.iter().filter(|&&n| n != usize::MAX).count(); - let mut gb: AHashMap> = - AHashMap::with_capacity_and_hasher(estimated_capacity, RandomState::new()); + let mut gb: HashMap>, BuildHasherDefault> = + HashMap::with_capacity_and_hasher(estimated_capacity, BuildHasherDefault::::default()); for (i, n) in gindices .into_iter() .enumerate() From 532fe2108c27889e7252a78dbf8eda83240937c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:36:12 +0000 Subject: [PATCH 4/4] style: apply rustfmt formatting Co-authored-by: samueltardieu <44656+samueltardieu@users.noreply.github.com> --- src/undirected/connected_components.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/undirected/connected_components.rs b/src/undirected/connected_components.rs index 1e695d9b..76ed4d50 100644 --- a/src/undirected/connected_components.rs +++ b/src/undirected/connected_components.rs @@ -107,8 +107,14 @@ where let (_, gindices) = Self::separate_components(groups); // Pre-size the hash map to reduce reallocations let estimated_capacity = gindices.iter().filter(|&&n| n != usize::MAX).count(); - let mut gb: HashMap>, BuildHasherDefault> = - HashMap::with_capacity_and_hasher(estimated_capacity, BuildHasherDefault::::default()); + let mut gb: HashMap< + usize, + HashSet>, + BuildHasherDefault, + > = HashMap::with_capacity_and_hasher( + estimated_capacity, + BuildHasherDefault::::default(), + ); for (i, n) in gindices .into_iter() .enumerate()