Default implementation of full_generation for GraphLike
parent
4d75a0bf97
commit
158ca093dd
121
src/lib.rs
121
src/lib.rs
|
|
@ -1,42 +1,127 @@
|
||||||
/// A trait for graph-like structures, where you can get a node by a unique value and find its neighbors.
|
/// A trait for graph-like structures, where you can get a node by a unique value and find its neighbors.
|
||||||
///
|
///
|
||||||
/// **Node** represents some settled data type
|
/// **Node** represents some settled data type. The graph should likely contain `Option<Node>`
|
||||||
|
/// elements prepopulated with `None` values. For example `[[None; WIDTH]; HEIGHT]` for a 2D grid.
|
||||||
///
|
///
|
||||||
/// **NodeId** represents something that can be used to find an Node inside the graph, such as
|
/// **NodeId** represents something that can be used to find an Node inside the graph, such as
|
||||||
/// position, index, or key to a hashmap. It should allow O(1) access to the Node.
|
/// position, index, or key to a hashmap. It should allow O(1) access to the Node.
|
||||||
pub trait GraphLike<Node, NodeId> {
|
pub trait GraphLike<Node, NodeId> {
|
||||||
|
/// Number of steps to backtrack after encountering an impossible node.
|
||||||
|
///
|
||||||
|
/// Used in the default implementation of `full_generation`
|
||||||
|
const BACKTRACK_AMOUNT: usize = 5;
|
||||||
|
/// Maximum number of times we try to retry before making progress. If exceeded, we give up.
|
||||||
|
///
|
||||||
|
/// Used in the default implementation of `full_generation`
|
||||||
|
const MAX_RETRIES: usize = 5;
|
||||||
|
|
||||||
fn get_node(&self, id: NodeId) -> Option<&Node>;
|
fn get_node(&self, id: NodeId) -> Option<&Node>;
|
||||||
fn set_node(&mut self, id: NodeId, node: Node);
|
fn set_node(&mut self, id: NodeId, node: Option<Node>);
|
||||||
fn iter_ids(&self) -> impl Iterator<Item = NodeId>;
|
fn iter_ids(&self) -> impl Iterator<Item = NodeId>;
|
||||||
fn iter_neighbor_ids(&self, id: NodeId) -> impl Iterator<Item = NodeId>;
|
fn iter_neighbor_ids(&self, id: NodeId) -> impl Iterator<Item = NodeId>;
|
||||||
|
|
||||||
fn iter_nodes<'a>(&'a self) -> impl Iterator<Item = (NodeId, Option<&'a Node>)> where Node: 'a, NodeId: Copy {
|
fn iter_nodes<'a>(&'a self) -> impl Iterator<Item = (NodeId, Option<&'a Node>)>
|
||||||
|
where
|
||||||
|
Node: 'a,
|
||||||
|
NodeId: Copy,
|
||||||
|
{
|
||||||
self.iter_ids().map(|id| (id, self.get_node(id)))
|
self.iter_ids().map(|id| (id, self.get_node(id)))
|
||||||
}
|
}
|
||||||
fn iter_empty(&self) -> impl Iterator<Item = NodeId> where NodeId: Copy {
|
fn iter_empty(&self) -> impl Iterator<Item = NodeId>
|
||||||
|
where
|
||||||
|
NodeId: Copy,
|
||||||
|
{
|
||||||
self.iter_ids().filter(|id| self.get_node(*id).is_none())
|
self.iter_ids().filter(|id| self.get_node(*id).is_none())
|
||||||
}
|
}
|
||||||
fn iter_neighbour_nodes(&self, id: NodeId) -> Vec<(NodeId, Option<&Node>)> where NodeId: Copy {
|
fn iter_neighbour_nodes(&self, id: NodeId) -> Vec<(NodeId, Option<&Node>)>
|
||||||
self.iter_neighbor_ids(id).map(|id| (id, self.get_node(id))).collect()
|
where
|
||||||
|
NodeId: Copy,
|
||||||
|
{
|
||||||
|
self.iter_neighbor_ids(id)
|
||||||
|
.map(|id| (id, self.get_node(id)))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: Replace return type with a result that explains the reason for failure. This could
|
fn single_step(
|
||||||
/// help with rollback to recover from impossible generation
|
&mut self,
|
||||||
fn single_step(&mut self, rules: &impl Ruleset<Node, NodeId>) -> Option<NodeId> where NodeId: Copy, Self: Sized {
|
rules: &impl Ruleset<Node, NodeId>,
|
||||||
let id = rules.find_lowest_entropy(self)?;
|
retry_count: Option<usize>,
|
||||||
let node = rules.choose(self, id).expect("Ruleset::choose should also return Some if Ruleset::entropy returns Some");
|
) -> Option<NodeId>
|
||||||
let empty_ids = self.iter_empty();
|
where
|
||||||
|
NodeId: Copy,
|
||||||
None
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let id = rules.find_lowest_entropy(self, retry_count)?;
|
||||||
|
let node = rules
|
||||||
|
.choose(self, id, retry_count)
|
||||||
|
.expect("Ruleset::choose should also return Some if Ruleset::entropy returns Some");
|
||||||
|
self.set_node(id, Some(node));
|
||||||
|
Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_generation(&mut self, rules: &impl Ruleset<Node, NodeId>) -> Result<(), ()>
|
||||||
|
where
|
||||||
|
NodeId: Copy,
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// Ordered list of all the nodes that we have generated. Used for backtracking.
|
||||||
|
let mut steps: Vec<NodeId> = vec![];
|
||||||
|
// Number of times we have backtracked from the same point
|
||||||
|
let mut retries: usize = 0;
|
||||||
|
// The biggest number of steps we have gotten se far
|
||||||
|
let mut most_steps = 0;
|
||||||
|
while self.iter_empty().next().is_some() {
|
||||||
|
match self.single_step(rules, Some(retries)) {
|
||||||
|
Some(id) => {
|
||||||
|
steps.push(id);
|
||||||
|
if steps.len() > most_steps {
|
||||||
|
most_steps = steps.len();
|
||||||
|
retries = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Backtracking
|
||||||
|
let reverse_steps =
|
||||||
|
steps.split_off(steps.len().saturating_sub(Self::BACKTRACK_AMOUNT));
|
||||||
|
for step in reverse_steps.into_iter().rev() {
|
||||||
|
self.set_node(step, None);
|
||||||
|
}
|
||||||
|
// Keep track of retries
|
||||||
|
retries += 1;
|
||||||
|
most_steps = steps.len();
|
||||||
|
if retries >= Self::MAX_RETRIES {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Ruleset<Node, NodeId> {
|
pub trait Ruleset<Node, NodeId> {
|
||||||
fn entropy(&self, graph: &impl GraphLike<Node, NodeId>, id: NodeId) -> Option<f32> where NodeId: Copy;
|
fn entropy(&self, graph: &impl GraphLike<Node, NodeId>, id: NodeId) -> Option<f32>
|
||||||
fn choose(&self, graph: &impl GraphLike<Node, NodeId>, id: NodeId) -> Option<Node> where NodeId: Copy;
|
where
|
||||||
|
NodeId: Copy;
|
||||||
|
fn choose(
|
||||||
|
&self,
|
||||||
|
graph: &impl GraphLike<Node, NodeId>,
|
||||||
|
id: NodeId,
|
||||||
|
_retry_count: Option<usize>,
|
||||||
|
) -> Option<Node>
|
||||||
|
where
|
||||||
|
NodeId: Copy;
|
||||||
|
|
||||||
fn find_lowest_entropy(&self, graph: &impl GraphLike<Node, NodeId>) -> Option<NodeId> where NodeId: Copy {
|
fn find_lowest_entropy(
|
||||||
graph.iter_empty()
|
&self,
|
||||||
|
graph: &impl GraphLike<Node, NodeId>,
|
||||||
|
_retry_count: Option<usize>,
|
||||||
|
) -> Option<NodeId>
|
||||||
|
where
|
||||||
|
NodeId: Copy,
|
||||||
|
{
|
||||||
|
graph
|
||||||
|
.iter_empty()
|
||||||
.map(|id| (id, self.entropy(graph, id)))
|
.map(|id| (id, self.entropy(graph, id)))
|
||||||
// NOTE: Option<f32> implements PartialOrd
|
// NOTE: Option<f32> implements PartialOrd
|
||||||
// None is considered smaller than any Some value
|
// None is considered smaller than any Some value
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue