diff --git a/src/directed/yen.rs b/src/directed/yen.rs index 908a1441..aac0d5ad 100644 --- a/src/directed/yen.rs +++ b/src/directed/yen.rs @@ -98,6 +98,48 @@ where /// 2); /// assert!(empty.is_empty()); /// ``` +/// +/// # Parallel edges +/// +/// A path is a sequence of nodes, so two routes differing only in which of several edges +/// between the same pair of nodes they take are the same path. Where more than one edge joins +/// two nodes, the cheaper one is the one taken, and the others are never reported: on the graph +/// below there is one path from `A` to `B`, not two, and asking for `k = 2` returns one. +/// +/// ``` +/// use pathfinding::prelude::yen; +/// +/// let two_roads = |c: &char| match c { +/// 'A' => vec![('B', 3), ('B', 2)], +/// _ => vec![], +/// }; +/// let routes = yen(&'A', two_roads, |c| *c == 'B', 2); +/// assert_eq!(routes, vec![(vec!['A', 'B'], 2)]); +/// ``` +/// +/// When the edges themselves matter, put enough into the node to tell them apart. Carrying the +/// edge a node was reached by makes the two routes different paths, and both are then found: +/// +/// ``` +/// use pathfinding::prelude::yen; +/// +/// // Two roads from A to B, costing 3 and 2. A node is a town plus the road reached it by, +/// // which the starting town has none of. +/// let roads = [('A', 'B', 3), ('A', 'B', 2)]; +/// let successors = |&(town, _): &(char, Option)| { +/// roads +/// .iter() +/// .enumerate() +/// .filter(move |(_, (from, _, _))| *from == town) +/// .map(|(road, &(_, to, cost))| ((to, Some(road)), cost)) +/// .collect::>() +/// }; +/// +/// let routes = yen(&('A', None), successors, |&(town, _)| town == 'B', 2); +/// assert_eq!(routes.len(), 2); +/// assert_eq!(routes[0], (vec![('A', None), ('B', Some(1))], 2)); +/// assert_eq!(routes[1], (vec![('A', None), ('B', Some(0))], 3)); +/// ``` pub fn yen( start: &N, mut successors: FN, diff --git a/tests/yen.rs b/tests/yen.rs index 82bc65ab..5e5fc482 100644 --- a/tests/yen.rs +++ b/tests/yen.rs @@ -212,3 +212,49 @@ fn parallel_edges_are_not_charged_twice() { vec![(vec!['0', 'a', 'c', 'd'], 3), (vec!['0', 'b', 'c', 'd'], 7)] ); } + +/// The graph from issue #516. A path is a sequence of nodes, so the two edges from `A` to `B` +/// describe one path and not two, and `yen` reports the cheaper of them. +#[test] +fn parallel_edges_describe_one_path() { + let two_roads = |c: &char| match c { + 'A' => vec![('B', 3), ('B', 2)], + _ => vec![], + }; + assert_eq!( + yen(&'A', two_roads, |c| *c == 'B', 2), + vec![(vec!['A', 'B'], 2)] + ); + + // The same holds in the middle of a longer graph: the cheaper edge is the one costed, and + // the dearer one does not become a second path through the same towns. + let longer = |c: &char| match c { + 'A' => vec![('B', 1), ('C', 1)], + 'B' => vec![('D', 5), ('D', 1)], + 'C' => vec![('D', 4)], + _ => vec![], + }; + assert_eq!( + yen(&'A', longer, |c| *c == 'D', 3), + vec![(vec!['A', 'B', 'D'], 2), (vec!['A', 'C', 'D'], 5)] + ); +} + +/// Carrying the edge a node was reached by makes routes over parallel edges distinct, which is +/// the modelling the documentation recommends when the individual edges matter. +#[test] +fn edges_in_the_node_make_parallel_routes_distinct() { + let roads = [('A', 'B', 3), ('A', 'B', 2)]; + let successors = |&(town, _): &(char, Option)| { + roads + .iter() + .enumerate() + .filter(move |(_, (from, _, _))| *from == town) + .map(|(road, &(_, to, cost))| ((to, Some(road)), cost)) + .collect::>() + }; + let routes = yen(&('A', None), successors, |&(town, _)| town == 'B', 2); + assert_eq!(routes.len(), 2, "both roads should be found"); + assert_eq!(routes[0], (vec![('A', None), ('B', Some(1))], 2)); + assert_eq!(routes[1], (vec![('A', None), ('B', Some(0))], 3)); +}