mapgraph/lib.rs
1//! A powerful library for directed adjacency list graphs.
2//!
3//! Graph is a complex data structure where relations between nodes may often be cyclic which
4//! doesn't go well with the Rust's type system. A typical approach to this is to refer to nodes and
5//! edges in a graph using node and edge indices (sometimes called IDs or keys) which are **mapped**
6//! to the actual nodes, edges and their weights instead of using references or pointers. Since
7//! we're **mapping** indices anyway why not make the **maps** we use generic and allow custom
8//! indices?
9//!
10//! `mapgraph` provides the generic [`Graph`] type that is based on two maps: one for nodes and
11//! one for edges. By using the rich system of traits from the [`map`] submodule the [`Graph`] type
12//! may borrow some capabilities of the maps it's based on. For example, using a [`HashMap`] as a
13//! node map for a [`Graph`] makes it accept external indices for nodes, effectively making the
14//! graph an index for its own nodes.
15//!
16//! # Library features and examples
17//!
18//! ## Internal node indices
19//!
20//! Let's imagine Alice and Bob are friends and often call each other. Sometimes they order pizza by
21//! phone from a place where Luigi works, but they aren't friends with Luigi, so he never calls them
22//! by himself.
23//!
24//! Here's one way to make a graph to represent their relations:
25//!
26//! ```
27//! use mapgraph::aliases::SlotMapGraph;
28//!
29//! # fn main() -> Result<(), mapgraph::error::AddEdgeError> {
30//! // we create a graph that uses SlotMaps for both nodes and edges
31//! let mut graph = SlotMapGraph::<&'static str, ()>::default();
32//!
33//! // next we create a node for each person
34//! let alice_index = graph.add_node("Alice");
35//! let bob_index = graph.add_node("Bob");
36//! let luigi_index = graph.add_node("Luigi");
37//!
38//! // let's ensure that the indices return us the right node weights
39//! assert_eq!(graph.node_weight(alice_index).copied(), Some("Alice"));
40//! assert_eq!(graph.node_weight(bob_index).copied(), Some("Bob"));
41//! assert_eq!(graph.node_weight(luigi_index).copied(), Some("Luigi"));
42//!
43//! // finally let's connect them
44//! graph.add_edge((), alice_index, bob_index)?;
45//! graph.add_edge((), bob_index, alice_index)?;
46//! graph.add_edge((), bob_index, luigi_index)?;
47//! graph.add_edge((), alice_index, luigi_index)?;
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! Here [`SlotMapGraph`] is just an alias for [`Graph`] that uses [`SlotMap`]s for both nodes and
53//! edges. We don't use edge weights, so we pass the unit type as the first parameter to
54//! [`Graph::add_edge`].
55//!
56//! While this may not seem like much just yet, using [`SlotMap`]s gives [`Graph`] important
57//! properties: removing nodes won't cause unrelated indices to be invalidated and key versioning
58//! prevents older indices from pointing to newly added nodes that happen to be stored in place of
59//! an older node.
60//!
61//! ## External node indices
62//!
63//! Now let's imagine we are telephone company employees who want to trace phone numbers to people
64//! and their relations for data science purposes. The obvious way to do this would be to create a
65//! separate index that maps phone numbers to graph indices, but that presents a challenge: the
66//! index should be synchronised to the graph. Adding or removing nodes should update the index as
67//! well as the graph itself. What if we used the phone numbers as node indices directly?
68//!
69//! Here's how we can do this:
70//!
71//! ```
72//! use mapgraph::aliases::HashSlotMapGraph;
73//!
74//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
75//! // we create a graph that uses a HashMap for nodes and a SlotMap for edges
76//! let mut graph = HashSlotMapGraph::<&'static str, (), _>::default();
77//!
78//! // define some constants for simplicity
79//! const ALICE_NUMBER: &'static str = "6661234";
80//! const BOB_NUMBER: &'static str = "5553535";
81//! const LUIGI_NUMBER: &'static str = "1001112";
82//!
83//! // next we create a node for each person
84//! graph.try_add_node_at_index(ALICE_NUMBER, "Alice")?;
85//! graph.try_add_node_at_index(BOB_NUMBER, "Bob")?;
86//! graph.try_add_node_at_index(LUIGI_NUMBER, "Luigi")?;
87//!
88//! // let's ensure that the indices return us the right node weights
89//! assert_eq!(graph.node_weight(ALICE_NUMBER).copied(), Some("Alice"));
90//! assert_eq!(graph.node_weight(BOB_NUMBER).copied(), Some("Bob"));
91//! assert_eq!(graph.node_weight(LUIGI_NUMBER).copied(), Some("Luigi"));
92//!
93//! // finally let's connect them
94//! graph.add_edge((), ALICE_NUMBER, BOB_NUMBER)?;
95//! graph.add_edge((), BOB_NUMBER, ALICE_NUMBER)?;
96//! graph.add_edge((), BOB_NUMBER, LUIGI_NUMBER)?;
97//! graph.add_edge((), ALICE_NUMBER, LUIGI_NUMBER)?;
98//! # Ok(())
99//! # }
100//! ```
101//!
102//! We use a [`HashSlotMapGraph`] which is just an alias to a [`Graph`] like [`SlotMapGraph`] from
103//! the previous example. The interface for adding nodes to a [`HashSlotMapGraph`] is, however,
104//! different. The [`Graph::try_add_node_at_index`] method that accepts an external node index is
105//! used rather than [`Graph::add_node`] which provides an index as its return value since
106//! [`HashMap`] accepts keys provided externally unlike [`SlotMap`] which produces its keys
107//! internally. The way your map treats indices is encoded by implementing either [`ExtKeyMap`] or
108//! [`IntKeyMap`] trait. See documentation for the [`map`] module for more info on map traits and
109//! the capabilities they expose.
110//!
111//! ## Ordered node indices
112//!
113//! For the last example let's pretend we also want to find people with particular area codes in
114//! their phone numbers:
115//!
116//! ```
117//! use mapgraph::aliases::BTreeSlotMapGraph;
118//!
119//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
120//! // we create a graph that uses a BTreeMap for nodes and a SlotMap for edges
121//! let mut graph = BTreeSlotMapGraph::<&'static str, (), _>::default();
122//!
123//! // define some constants for simplicity
124//! const ALICE_NUMBER: &'static str = "6661234";
125//! const BOB_NUMBER: &'static str = "5553535";
126//! const LUIGI_NUMBER: &'static str = "1001112";
127//!
128//! // next we create a node for each person
129//! graph.try_add_node_at_index(ALICE_NUMBER, "Alice")?;
130//! graph.try_add_node_at_index(BOB_NUMBER, "Bob")?;
131//! graph.try_add_node_at_index(LUIGI_NUMBER, "Luigi").unwrap();
132//!
133//! // we skip adding edges this time and just do a range query
134//! let phones_and_names: Vec<_> = graph.nodes_range("5000000"..="7000000").collect();
135//!
136//! // check that the results match our expectations
137//! assert_eq!(
138//! &phones_and_names,
139//! &[(BOB_NUMBER, &"Bob"), (ALICE_NUMBER, &"Alice")]
140//! );
141//! # Ok(())
142//! # }
143//! ```
144//!
145//! Here we use the capabilities of a [`BTreeMap`] that are lent to the graph using the
146//! [`OrderedKeyMap`] trait.
147//!
148//! # More advanced features
149//!
150//! ## Frozen graphs
151//!
152//! This is a nice feature lent from the [`petgraph`] crate. [`FrozenGraph`] is a [`Graph`] with an
153//! immutable structure. Having a mutable reference to a [`FrozenGraph`] means you can mutate
154//! weights, but not add or remove nodes and edges. [`Graph`] and [`FrozenGraph`] can be freely
155//! converted between each other, graph also implements [`Deref<Target=FrozenGraph>`]. See docs for
156//! [`FrozenGraph`] for more info.
157//!
158//! ## Parallel iteration using [`rayon`]
159//!
160//! When the `rayon` crate feature is enabled, [`Graph`] exposes methods for parallel iteration of
161//! nodes and edges in case the underlying maps expose this functionality through the [`ParIterMap`]
162//! trait.
163//!
164//! ```
165//! use mapgraph::aliases::HashSlotMapGraph;
166//! use rayon::iter::ParallelIterator;
167//!
168//! let mut graph = HashSlotMapGraph::<i32, i32, &'static str>::default();
169//!
170//! // add nodes and edges...
171//!
172//! // add 1 to each node weight in parallel
173//! graph
174//! .par_iter_node_weights_mut()
175//! .for_each(|(_, weight)| *weight += 1);
176//! ```
177//!
178//! In case you need to mutably iterate nodes and edges in parallel or to walk over edges while
179//! doing parallel iteration on nodes (or vice versa), a mutable reference to a [`Graph`] may be
180//! split into a reference to [`Nodes`] and a reference to [`Edges`]:
181//!
182//! ```
183//! use mapgraph::{aliases::HashSlotMapGraph, graph::iter::EdgesWalker};
184//! use rayon::iter::ParallelIterator;
185//!
186//! let mut graph = HashSlotMapGraph::<i32, i32, &'static str>::default();
187//!
188//! // add nodes and edges...
189//!
190//! // split a reference to a graph
191//! let (nodes, edges) = graph.as_nodes_mut_and_edges();
192//!
193//! nodes.par_iter_mut().for_each(|(_, node)| {
194//! // let's sum all the weight of outgoing edges of that node
195//! let mut outputs_walker = node.walk_outputs();
196//! let mut sum = 0;
197//! while let Some((_ei, edge)) = outputs_walker.walk_edges_next(edges) {
198//! sum += *edge.weight();
199//! }
200//!
201//! // add that sum to the node's weight
202//! *node.weight_mut() += sum;
203//! });
204//! ```
205//!
206//! # Crate features
207//!
208//! * `std` - enables support for the [`HashMap`] type, provides implementations of
209//! [`std::error::Error`] on error types, enables the `std` feature on the [`slotmap`] crate if the
210//! `slotmap` feature is enabled. Requires the `alloc` feature.
211//! * `alloc` - enables support for the [`alloc`] crate, in particular [`BTreeMap`] and [`BTreeSet`]
212//! types.
213//! * `slotmap` - enables integration with the [`slotmap`] crate.
214//! * `indexmap` - enables integration with the [`indexmap`] crate.
215//! * `algorithms` - enables the [`algo`] module which provides various algorithms to be run on
216//! graphs.
217//! * `serde` - enables serialization/deserialization using [`serde`]. Disabled by default.
218//! * `rayon` - enables parallel iteration support using [`rayon`]. Disabled by default.
219//!
220//! [`petgraph`]: https://github.com/petgraph/petgraph
221//! [`HashMap`]: std::collections::HashMap
222//! [`BTreeMap`]: alloc::collections::BTreeMap
223//! [`BTreeSet`]: alloc::collections::BTreeSet
224//! [`SlotMap`]: slotmap::SlotMap
225//! [`IndexMap`]: indexmap::IndexMap
226//! [`SlotMapGraph`]: aliases::SlotMapGraph
227//! [`HashSlotMapGraph`]: aliases::HashSlotMapGraph
228//! [`Deref<Target=FrozenGraph>`]: std::ops::Deref
229//! [`Nodes`]: graph::Nodes
230//! [`Edges`]: graph::Edges
231
232#![no_std]
233#![cfg_attr(docsrs, feature(doc_auto_cfg))]
234#![deny(missing_docs)]
235#![deny(missing_debug_implementations)]
236#![deny(rust_2018_idioms)]
237#![deny(unreachable_pub)]
238#![deny(unsafe_code)]
239#![deny(clippy::unwrap_used)]
240#![deny(clippy::iter_without_into_iter)]
241#![warn(clippy::missing_errors_doc)]
242#![warn(clippy::missing_panics_doc)]
243
244#[cfg(feature = "alloc")]
245extern crate alloc;
246#[cfg(feature = "std")]
247extern crate std;
248
249#[cfg(feature = "algorithms")]
250pub mod algo;
251#[cfg(any(feature = "alloc", feature = "slotmap"))]
252pub mod aliases;
253pub mod error;
254pub mod graph;
255pub mod map;
256mod util;
257
258pub use graph::{FrozenGraph, Graph};
259#[cfg(feature = "rayon")]
260pub use map::ParIterMap;
261pub use map::{ExtKeyMap, IntKeyMap, KeySet, Map, MapWithCapacity, OrderedKeyMap};