Skip to content

Commit 28db087

Browse files
Merge #324
324: dfs reach r=samueltardieu a=samueltardieu - Add dfs_reach() - Deprecate Matrix::reachable() for bfs_reachable() and dfs_reachable() Co-authored-by: Samuel Tardieu <[email protected]>
2 parents 1deec00 + 32eba04 commit 28db087

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

src/directed/dfs.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Compute a path using the [depth-first search
22
//! algorithm](https://en.wikipedia.org/wiki/Depth-first_search).
33
4+
use std::collections::HashSet;
5+
use std::hash::Hash;
6+
47
/// Compute a path using the [depth-first search
58
/// algorithm](https://en.wikipedia.org/wiki/Depth-first_search).
69
/// The path starts from `start` up to a node for which `success` returns `true` is computed and
@@ -77,3 +80,83 @@ where
7780
false
7881
}
7982
}
83+
84+
/// Visit all nodes that are reachable from a start node. The node will be visited
85+
/// in DFS order, starting from the `start` node and following the order returned
86+
/// by the `successors` function.
87+
///
88+
/// # Examples
89+
///
90+
/// The iterator stops when there are no new nodes to visit:
91+
///
92+
/// ```
93+
/// use pathfinding::prelude::dfs_reach;
94+
///
95+
/// let all_nodes = dfs_reach(3, |_| (1..=5)).collect::<Vec<_>>();
96+
/// assert_eq!(all_nodes, vec![3, 1, 2, 4, 5]);
97+
/// ```
98+
///
99+
/// The iterator can be used as a generator. Here are for examples
100+
/// the multiples of 2 and 3 smaller than 15 (although not in
101+
/// natural order but in the order they are discovered by the DFS
102+
/// algorithm):
103+
///
104+
/// ```
105+
/// use pathfinding::prelude::dfs_reach;
106+
///
107+
/// let mut it = dfs_reach(1, |&n| vec![n*2, n*3].into_iter().filter(|&x| x < 15)).skip(1);
108+
/// assert_eq!(it.next(), Some(2)); // 1*2
109+
/// assert_eq!(it.next(), Some(4)); // (1*2)*2
110+
/// assert_eq!(it.next(), Some(8)); // ((1*2)*2)*2
111+
/// assert_eq!(it.next(), Some(12)); // ((1*2)*2)*3
112+
/// assert_eq!(it.next(), Some(6)); // (1*2)*3
113+
/// assert_eq!(it.next(), Some(3)); // 1*3
114+
/// // (1*3)*2 == 6 which has been seen already
115+
/// assert_eq!(it.next(), Some(9)); // (1*3)*3
116+
/// ```
117+
pub fn dfs_reach<N, FN, IN>(start: N, successors: FN) -> DfsReachable<N, FN>
118+
where
119+
N: Eq + Hash + Clone,
120+
FN: FnMut(&N) -> IN,
121+
IN: IntoIterator<Item = N>,
122+
{
123+
let (to_see, mut seen) = (vec![start.clone()], HashSet::new());
124+
seen.insert(start);
125+
DfsReachable {
126+
to_see,
127+
seen,
128+
successors,
129+
}
130+
}
131+
132+
/// Struct returned by [`dfs_reach`](crate::directed::dfs::dfs_reach).
133+
pub struct DfsReachable<N, FN> {
134+
to_see: Vec<N>,
135+
seen: HashSet<N>,
136+
successors: FN,
137+
}
138+
139+
impl<N, FN, IN> Iterator for DfsReachable<N, FN>
140+
where
141+
N: Eq + Hash + Clone,
142+
FN: FnMut(&N) -> IN,
143+
IN: IntoIterator<Item = N>,
144+
{
145+
type Item = N;
146+
147+
fn next(&mut self) -> Option<Self::Item> {
148+
if let Some(n) = self.to_see.pop() {
149+
let mut to_insert = Vec::new();
150+
for s in (self.successors)(&n) {
151+
if !self.seen.contains(&s) {
152+
to_insert.push(s.clone());
153+
self.seen.insert(s);
154+
}
155+
}
156+
self.to_see.extend(to_insert.into_iter().rev());
157+
Some(n)
158+
} else {
159+
None
160+
}
161+
}
162+
}

src/matrix.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Matrix of an arbitrary type and utilities to rotate, transpose, etc.
22
33
use crate::directed::bfs::bfs_reach;
4+
use crate::directed::dfs::dfs_reach;
45
use crate::utils::uint_sqrt;
56
use itertools::iproduct;
67
use itertools::Itertools;
@@ -534,7 +535,37 @@ impl<C> Matrix<C> {
534535
/// to implement a flood-filling algorithm. Since the indices are collected
535536
/// into a vector, they can later be used without keeping a reference on the
536537
/// matrix itself, e.g., to modify the matrix.
538+
///
539+
/// This method calls the [`bfs_reachable()`](`Self::bfs_reachable`) method to
540+
/// do its work.
541+
#[deprecated(
542+
since = "3.0.7",
543+
note = "Use `bfs_reachable()` or `dfs_reachable()` methods instead"
544+
)]
537545
pub fn reachable<P>(
546+
&self,
547+
start: (usize, usize),
548+
diagonals: bool,
549+
predicate: P,
550+
) -> BTreeSet<(usize, usize)>
551+
where
552+
P: FnMut((usize, usize)) -> bool + Copy,
553+
{
554+
self.bfs_reachable(start, diagonals, predicate)
555+
}
556+
557+
/// Return a set of the indices reachable from a candidate starting point
558+
/// and for which the given predicate is valid. This can be used for example
559+
/// to implement a flood-filling algorithm. Since the indices are collected
560+
/// into a vector, they can later be used without keeping a reference on the
561+
/// matrix itself, e.g., to modify the matrix.
562+
///
563+
/// The search is done using a breadth first search (BFS) algorithm.
564+
///
565+
/// # See also
566+
///
567+
/// The [`dfs_reachable()`](`Self::dfs_reachable`) performs a DFS search instead.
568+
pub fn bfs_reachable<P>(
538569
&self,
539570
start: (usize, usize),
540571
diagonals: bool,
@@ -548,6 +579,32 @@ impl<C> Matrix<C> {
548579
})
549580
.collect()
550581
}
582+
583+
/// Return a set of the indices reachable from a candidate starting point
584+
/// and for which the given predicate is valid. This can be used for example
585+
/// to implement a flood-filling algorithm. Since the indices are collected
586+
/// into a vector, they can later be used without keeping a reference on the
587+
/// matrix itself, e.g., to modify the matrix.
588+
///
589+
/// The search is done using a depth first search (DFS) algorithm.
590+
///
591+
/// # See also
592+
///
593+
/// The [`bfs_reachable()`](`Self::bfs_reachable`) performs a BFS search instead.
594+
pub fn dfs_reachable<P>(
595+
&self,
596+
start: (usize, usize),
597+
diagonals: bool,
598+
mut predicate: P,
599+
) -> BTreeSet<(usize, usize)>
600+
where
601+
P: FnMut((usize, usize)) -> bool + Copy,
602+
{
603+
dfs_reach(start, |&n| {
604+
self.neighbours(n, diagonals).filter(move |&n| predicate(n))
605+
})
606+
.collect()
607+
}
551608
}
552609

553610
impl<C> Index<(usize, usize)> for Matrix<C> {

tests/matrix.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ fn empty_neighbours() {
380380
}
381381

382382
#[test]
383+
#[allow(deprecated)]
383384
fn reachable() {
384385
let m = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
385386

@@ -396,6 +397,40 @@ fn reachable() {
396397
);
397398
}
398399

400+
#[test]
401+
fn bfs_reachable() {
402+
let m = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
403+
404+
let indices = m.bfs_reachable((1, 0), false, |n| m[n] % 4 != 0);
405+
assert_eq!(
406+
indices.into_iter().collect::<Vec<_>>(),
407+
vec![(1, 0), (2, 0), (2, 1)]
408+
);
409+
410+
let indices = m.bfs_reachable((1, 0), true, |n| m[n] % 4 != 0);
411+
assert_eq!(
412+
indices.into_iter().collect::<Vec<_>>(),
413+
vec![(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
414+
);
415+
}
416+
417+
#[test]
418+
fn dfs_reachable() {
419+
let m = matrix![[0, 1, 2], [3, 4, 5], [6, 7, 8]];
420+
421+
let indices = m.dfs_reachable((1, 0), false, |n| m[n] % 4 != 0);
422+
assert_eq!(
423+
indices.into_iter().collect::<Vec<_>>(),
424+
vec![(1, 0), (2, 0), (2, 1)]
425+
);
426+
427+
let indices = m.dfs_reachable((1, 0), true, |n| m[n] % 4 != 0);
428+
assert_eq!(
429+
indices.into_iter().collect::<Vec<_>>(),
430+
vec![(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
431+
);
432+
}
433+
399434
#[test]
400435
fn bounds_test() {
401436
let mut m = matrix![[0, 1, 2], [3, 4, 5]];

0 commit comments

Comments
 (0)