1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
//! # space-search
//!
//! A library providing basic generic depth-first, breadth-first, heuristic-guided, and A* based search space exploration algorithms.
//!
//! Implement [`Searchable`] + [`SolutionIdentifiable`] to perform breadth-first or depth-first searching. Implement [`Scoreable`] as well to perform heuristically guided search space exploration. Finally, additionally implement [`CostSearchable`] to perform A* based search exploration. Pass them to [`Searcher`] to create an iterator that will search for a solution.
//!
//! [`Searcher`] requires that you specify a `Manager` type that determines the strategy, return result, and optimization of the search algorithm. Choose one of the searchers defined in the hierarchy of the [`search`] module to fit your individual needs.
//!
//! * Implement [`Scoreable`] to utilize the `guided` search strategy based managers, which will prioritize searching states with a lower associated score first. Additionally, implement [`CostSearchable`] to make use of the A* based search managers in the `a_star` module. If implementing [`Scoreable`] is too complex or unnecessary for your use case, then you may use the `unguided` search managers, which explore the space naively in a depth-first or breadth-first manner, toggleable by a flag on the manager itself.
//! * Use a `route` based manager to yield results consisting of the sequence of steps taken from the starting state to the ending state. Use a `no_route` manager to just yield the solution state alone. Route based managers require that your state type implement [`Clone`].
//! * Implement [`Eq`] + [`std::hash::Hash`] + [`Clone`] for your [`Searchable`] type to benefit from prior explored state checking optimization using a `hashable` manager; if youre unable to, then use an `unhashable` manager, which does not require these additional bounds, but will likely explore the space much less efficiently unless cyclic traversal is not an inherent property of your search space.
//!
//! When implementing [`Scoreable`], make sure that lower scoring states are closer to a solution.
//!
//! ```
//! use space_search::*;
//! use std::{vec, hash::Hash};
//!
//! #[derive(Clone, Debug, PartialEq, Eq, Hash)]
//! struct Pos(i32, i32);
//!
//! impl Searchable for Pos {
//! fn next_states(&self) -> impl Iterator<Item = Self> {
//! let &Pos(x, y) = self;
//! [
//! Pos(x - 1, y),
//! Pos(x, y - 1),
//! Pos(x + 1, y),
//! Pos(x, y + 1),
//! ].into_iter()
//! }
//! }
//!
//! impl SolutionIdentifiable for Pos {
//! fn is_solution(&self) -> bool {
//! let &Pos(x, y) = self;
//! x == 5 && y == 5
//! }
//! }
//!
//! let mut searcher: Searcher<search::unguided::no_route::hashable::Manager<_>, _> = Searcher::new(Pos(0, 0));
//! assert_eq!(searcher.next(), Some(Pos(5, 5)));
//! ```
use std::{collections::VecDeque, marker::PhantomData};
pub mod search;
/// Basic trait for depth-first and breadth-first search space exploration.
///
/// Implement this + [`SolutionIdentifiable`] for your state type to perform search-space exploration.
pub trait Searchable: Sized {
/// Yield all adjacent explorable states reachable from this state.
fn next_states(&self) -> impl Iterator<Item = Self>;
}
/// Trait that allows a state to be identified as a solution.
///
/// Implement this + [`Searchable`] for your state type to perform search-space exploration.
pub trait SolutionIdentifiable {
/// Return `true` if this state is a solution state.
fn is_solution(&self) -> bool;
}
/// Trait for search space exploration guided by a heuristic.
///
/// Implement this + [`Searchable`] + [`SolutionIdentifiable`] to perform heuristically-guided search-space exploration.
///
/// New states are explored in the order of
/// lowest-scoring first, biasing the search exploration in the direction of a solution. Ensure the scores
/// returned by [`Scoreable::score`] are decreasing with the proximity to a solution.
pub trait Scoreable {
/// Type used to represent a state's score.
/// Common types can be [`i32`], [`usize`], an ordered float type, or your own type implementing [`Ord`].
type Score: Ord;
/// Score function used for heuristic exploration. New states are explored in the order of
/// lowest-scoring first; ensure the scores
/// returned by this function decrease with the proximity to a solution.
fn score(&self) -> Self::Score;
}
/// Trait for search space exploration guided by a cost function & heuristic.
///
/// Implement this + [`Scoreable`] + [`SolutionIdentifiable`] to perform A* guided search-space exploration.
///
/// [`Searchable`] is automatically implemented if this trait is implemented.
pub trait CostSearchable: Scoreable + Sized {
/// Yield all adjacent explorable states reachable from this state, paired with the associated cost
/// of traversing from the current state each new state.
fn next_states_with_costs(&self) -> impl Iterator<Item = (Self, Self::Score)>;
}
// this doesnt work unfortunately :(
// we can only have one blanket impl or the other
// /// Marker trait to auto-implement [`CostSearchable`] for any type that already implements [`Scoreable`] + [`Searchable`]; the cost
// /// is assumed to be 1 for all states returned by [`Searchable::next_states`].
// pub trait CostSearchableForSearchable {}
// impl<T> CostSearchable for T
// where
// T: Scoreable + Searchable + CostSearchableForSearchable,
// T::Score: One,
// {
// fn next_states_with_costs(&self) -> impl Iterator<Item = (Self, Self::Score)> {
// self.next_states().map(|s| (s, T::Score::one()))
// }
// }
impl<T> Searchable for T
where
T: CostSearchable,
{
fn next_states(&self) -> impl Iterator<Item = Self> {
self.next_states_with_costs().map(|(s, _)| s)
}
}
/// Internal.
///
/// Used to represent states paired with their scores in guided exploration managers.
struct OrderedSearchable<T, C> {
state: T,
score: C,
}
impl<S> From<S> for OrderedSearchable<S, S::Score>
where
S: Scoreable,
{
fn from(state: S) -> Self {
let score = state.score();
OrderedSearchable { state, score }
}
}
impl<S> From<StateParent<S>> for OrderedSearchable<StateParent<S>, S::Score>
where
S: Scoreable,
{
fn from(pair: StateParent<S>) -> Self {
let score = pair.state.score();
OrderedSearchable { state: pair, score }
}
}
impl<T, C> PartialEq for OrderedSearchable<T, C>
where
C: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.score == other.score
}
}
impl<T, C> Eq for OrderedSearchable<T, C> where C: Eq {}
impl<T, C> PartialOrd for OrderedSearchable<T, C>
where
C: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
other.score.partial_cmp(&self.score)
}
}
impl<T, C> Ord for OrderedSearchable<T, C>
where
C: Ord,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.score.cmp(&self.score)
}
}
/// Internal.
///
/// Used to represent states with no additional context in solution-only yielding managers.
pub struct NoContext<S>(S);
impl<S> AsRef<S> for NoContext<S> {
fn as_ref(&self) -> &S {
&self.0
}
}
/// Internal.
///
/// Used to represent states with the added context of their parent state
/// in solution-route yielding managers.
#[derive(Clone)]
pub struct StateParent<S> {
state: S,
parent: Option<usize>,
}
impl<S> AsRef<S> for StateParent<S> {
fn as_ref(&self) -> &S {
&self.state
}
}
/// Internal.
///
/// Used to represent states with the added context of their
/// cumulative rolling cost for A* based managers.
pub struct StateCumulativeCost<S, C> {
state: S,
cumulative_cost: C,
}
impl<S, C> AsRef<S> for StateCumulativeCost<S, C> {
fn as_ref(&self) -> &S {
&self.state
}
}
/// Internal.
///
/// Used to represent states with the added context of their parent state &
/// cumulative rolling cost for A* based solution route-yielding managers.
#[derive(Clone)]
pub struct StateParentCumulativeCost<S, C> {
state: S,
parent: Option<usize>,
cumulative_cost: C,
}
impl<S, C> AsRef<S> for StateParentCumulativeCost<S, C> {
fn as_ref(&self) -> &S {
&self.state
}
}
impl<S, C> From<StateParentCumulativeCost<S, C>> for StateParent<S> {
fn from(
StateParentCumulativeCost { state, parent, .. }: StateParentCumulativeCost<S, C>,
) -> Self {
StateParent { state, parent }
}
}
/// Internal.
///
/// Trait abstracting all exploration managers' common functionality.
pub trait ExplorationManager<S> {
type YieldResult;
type FringeItem: AsRef<S>;
type CurrentStateContext;
type NextStatesIterItem;
fn initialize(initial_state: S) -> Self;
fn pop_state(&mut self) -> Option<Self::FringeItem>;
fn prepare_result_from(&self, item: Self::FringeItem) -> Self::YieldResult;
fn valid_state(&mut self, item: &Self::FringeItem) -> bool;
fn place_state(&mut self, item: Self::FringeItem);
fn register_current_state(&mut self, item: &Self::FringeItem) -> Self::CurrentStateContext;
fn prepare_state(
&self,
context: &Self::CurrentStateContext,
state: Self::NextStatesIterItem,
) -> Self::FringeItem;
fn next_states_iter(current_state: &S) -> impl Iterator<Item = Self::NextStatesIterItem>;
}
/// State space exploration iterator.
///
/// Create an instance of this to explore a search space.
pub struct Searcher<M, S> {
pub manager: M,
_marker: PhantomData<S>,
}
impl<M, S> Searcher<M, S> {
/// Create a new search iterator from an initial state.
pub fn new(initial_state: S) -> Self
where
M: ExplorationManager<S>,
{
Self {
manager: M::initialize(initial_state),
_marker: PhantomData,
}
}
/// Create a new search iterator from a default initial state.
pub fn new_with_default() -> Self
where
S: Default,
M: ExplorationManager<S>,
{
Self::new(Default::default())
}
}
impl<M, S> Iterator for Searcher<M, S>
where
M: ExplorationManager<S>,
S: SolutionIdentifiable,
{
type Item = M::YieldResult;
fn next(&mut self) -> Option<Self::Item> {
loop {
let current_state = self.manager.pop_state()?;
if current_state.as_ref().is_solution() {
return Some(self.manager.prepare_result_from(current_state));
}
let context = self.manager.register_current_state(¤t_state);
for item in M::next_states_iter(current_state.as_ref()) {
let new_item = self.manager.prepare_state(&context, item);
if self.manager.valid_state(&new_item) {
self.manager.place_state(new_item);
}
}
}
}
}
fn prepare_result_from_state_parent_map<S>(
parents: &Vec<StateParent<S>>,
StateParent {
mut state,
parent: mut maybe_parent_index,
}: StateParent<S>,
) -> Vec<S>
where
S: Clone,
{
let mut result = VecDeque::new();
while let Some(parent_index) = maybe_parent_index {
result.push_front(state);
let StateParent {
state: new_state,
parent: new_parent_index,
} = parents
.get(parent_index)
.expect("Parent state will always exist if parent index exists")
.clone();
state = new_state;
maybe_parent_index = new_parent_index;
}
result.push_front(state);
result.into()
}