use rand::prelude::*;
use std::{
collections::HashMap,
error::Error,
fmt::{Debug, Formatter, Result as FmtResult},
hash::{Hash, Hasher},
sync::{Arc, RwLock},
};
pub type ToStep<T> = Arc<Step<T>>;
#[derive(Default)]
pub struct Step<T: Eq + Copy + Hash + Debug + Send + Sync> {
pub state: T,
pub transitions: RwLock<HashMap<ToStep<T>, usize>>,
}
impl<T> Clone for Step<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
fn clone(&self) -> Self {
#[allow(clippy::mutable_key_type)]
let transitions = self.transitions.read().unwrap().clone();
Step {
state: self.state,
transitions: RwLock::new(transitions),
}
}
}
impl<T> Debug for Step<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "Step {{ state: {:?} }}", self.state)
}
}
impl<T> Hash for Step<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.state.hash(hasher);
}
}
impl<T> PartialEq for Step<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
fn eq(&self, other: &Self) -> bool {
self.state == other.state
}
}
impl<T> Eq for Step<T> where T: Eq + Copy + Hash + Debug + Send + Sync {}
impl<T> Step<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
pub fn new(state: T) -> Self {
Step {
state,
transitions: RwLock::new(HashMap::new()),
}
}
pub fn insert_transition(&self, to_step: ToStep<T>, weight: usize) {
self.transitions.write().unwrap().insert(to_step, weight);
}
pub fn next(&self) -> Option<ToStep<T>> {
let mut rng = rand::rng();
let transitions = self.transitions.read().unwrap();
if transitions.is_empty() {
return None;
}
let total: usize = transitions.values().sum();
if total == 0 {
return None;
}
let roll = rng.random_range(0..total);
let mut cumulative = 0;
transitions.iter().find_map(|(to_step, &weight)| {
cumulative += weight;
if roll < cumulative {
Some(Arc::clone(to_step))
} else {
None
}
})
}
}
pub fn walk<T>(start: ToStep<T>, steps: usize) -> Vec<T>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
{
let mut current = start;
let mut path = vec![current.state];
for _ in 1..steps {
if let Some(next) = current.next() {
path.push(next.state);
current = next;
} else {
break;
}
}
path
}
pub fn mut_walk<T, F>(start: ToStep<T>, steps: usize, apply: F) -> Result<Vec<T>, Box<dyn Error>>
where
T: Eq + Copy + Hash + Debug + Send + Sync,
F: Fn(ToStep<T>, ToStep<T>) -> Result<(), Box<dyn Error>>,
{
let mut current = start;
let mut path = vec![current.state];
for _ in 1..steps {
if let Some(next) = current.next() {
apply(current.clone(), next.clone())?;
path.push(current.state);
current = next;
} else {
break;
}
}
Ok(path)
}