searchlib 0.1.2

Satisficing and optimal search algorithms
Documentation
//! [Breadth First Search](https://en.wikipedia.org/wiki/Breadth-first_search)

use crate::{trace, FxIndexMap};
use indexmap::map::Entry::Vacant;
use std::hash::Hash;

/// A BFS search functor. Iterating upon it steps through the BFS search, where each step is one
/// expansion and evaluation. Each step returns an optional optional path, which is Some if it has found a
/// path to a goal state, and None if not. A step returns None if it has explored the whole
/// reachable state space.
///
/// # Example
/// This example show cases the `solve` function. Which returns the first found solution, or none
/// if no such solution is found.
/// ```
/// use searchlib::bfs::BFS;
/// use std::hash::Hash;
///
/// pub fn solve<STATE, SUCCESSORS, SUCCESS, ITER>(
///     init: &STATE,
///     successors: SUCCESSORS,
///     success: SUCCESS,
/// ) -> Option<Vec<STATE>>
/// where
///     STATE: Eq + Hash + Clone,
///     SUCCESSORS: Fn(&STATE) -> ITER,
///     SUCCESS: Fn(&STATE) -> bool,
///     ITER: IntoIterator<Item = STATE>,
/// {
///     BFS::new(init, successors, success)
///         .into_iter()
///         .find(|i| i.is_some())?
/// }
/// ```
pub struct BFS<STATE, SUCCESSORS, SUCCESS, ITER>
where
    STATE: Eq + Hash + Clone,
    SUCCESSORS: FnMut(&STATE) -> ITER,
    SUCCESS: FnMut(&STATE) -> bool,
    ITER: IntoIterator<Item = STATE>,
{
    index: usize,
    states: FxIndexMap<STATE, usize>,
    successors: SUCCESSORS,
    success: SUCCESS,
}

impl<STATE, SUCCESSORS, SUCCESS, ITER> BFS<STATE, SUCCESSORS, SUCCESS, ITER>
where
    STATE: Eq + Hash + Clone,
    SUCCESSORS: FnMut(&STATE) -> ITER,
    SUCCESS: FnMut(&STATE) -> bool,
    ITER: IntoIterator<Item = STATE>,
{
    pub fn new(init: &STATE, successors: SUCCESSORS, success: SUCCESS) -> Self {
        let mut states = FxIndexMap::default();
        states.insert(init.clone(), 0);
        Self {
            index: 0,
            states,
            successors,
            success,
        }
    }

    pub fn len(&self) -> usize {
        self.states.len()
    }
}

impl<STATE, SUCCESSORS, SUCCESS, ITER> Iterator for BFS<STATE, SUCCESSORS, SUCCESS, ITER>
where
    STATE: Eq + Hash + Clone,
    SUCCESSORS: FnMut(&STATE) -> ITER,
    SUCCESS: FnMut(&STATE) -> bool,
    ITER: IntoIterator<Item = STATE>,
{
    type Item = Option<Vec<STATE>>;

    fn next(&mut self) -> Option<Self::Item> {
        let (node, _) = self.states.get_index(self.index)?;
        for successor in (self.successors)(node) {
            if (self.success)(&successor) {
                let mut path = trace(&self.states, self.index);
                path.push(successor);
                self.index += 1;
                return Some(Some(path));
            }
            if let Vacant(e) = self.states.entry(successor) {
                e.insert(self.index);
            }
        }
        self.index += 1;
        Some(None)
    }
}

/// Explores the state space reachable from `init` in a breadth first manner. Upon reaching a state
/// which results in `success`, returns the path of states travelled.
/// If no such state is reachable, None is returned.
///
/// - `init` is the initial state
/// - `successors` returns the successor states for some state
/// - `success` returns whether some state is a goal state
///
/// # Example
/// An example which showcases a borked version of 'Knight's tour'; a famous chess problem.
/// The original problem is to find a path in which a knight visits all squares exactly once.
/// Whereas, in this version the knight simply has to travel from square (1, 1) to (4, 6).
///
/// However, do note: Technically the knight can move outside the board.
/// ```
/// use searchlib::bfs::solve;
/// const INIT: (isize, isize) = (1, 1);
/// const GOAL: (isize, isize) = (4, 6);
///
/// fn successors(state: &(isize, isize)) -> Vec<(isize, isize)> {
///     let (x, y) = state;
///     vec![(x + 1, y + 2), (x + 1, y - 2),(x - 1, y + 2), (x - 1, y - 2),
///          (x + 2, y + 1), (x + 2, y - 1),(x - 2, y + 1), (x - 2, y - 1)]
/// }
///
/// let result = solve(&INIT, successors, |&s| s == GOAL);
/// assert_eq!(result.expect("unsolvable").len(), 5);
/// ```
pub fn solve<STATE, SUCCESSORS, SUCCESS, ITER>(
    init: &STATE,
    successors: SUCCESSORS,
    success: SUCCESS,
) -> Option<Vec<STATE>>
where
    STATE: Eq + Hash + Clone,
    SUCCESSORS: FnMut(&STATE) -> ITER,
    SUCCESS: FnMut(&STATE) -> bool,
    ITER: IntoIterator<Item = STATE>,
{
    BFS::new(init, successors, success)
        .into_iter()
        .find(|i| i.is_some())?
}

/// Explores the state space reachable from `init` in a breadth first manner. It returns an
/// iterator over those states that result in `success`. If no such state exists it is an empty
/// iterator.
///
/// - `init` is the initial state
/// - `successors` returns the successor states for some state
/// - `success` returns whether some state is a goal state
pub fn paths<STATE, SUCCESSORS, SUCCESS, ITER>(
    init: &STATE,
    successors: SUCCESSORS,
    success: SUCCESS,
) -> impl Iterator<Item = Vec<STATE>>
where
    STATE: Eq + Hash + Clone,
    SUCCESSORS: FnMut(&STATE) -> ITER,
    SUCCESS: FnMut(&STATE) -> bool,
    ITER: IntoIterator<Item = STATE>,
{
    BFS::new(init, successors, success)
        .into_iter()
        .filter_map(|i| i)
}