deleted arena allocator for the tree
This commit is contained in:
parent
b437cefe5c
commit
25d72e9633
5 changed files with 115 additions and 158 deletions
160
src/dice.rs
160
src/dice.rs
|
|
@ -1,68 +1,68 @@
|
|||
use anyhow::bail;
|
||||
use id_arena::Arena;
|
||||
use rand::{Rng, RngExt};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expression {
|
||||
pub(crate) arena: Arena<AstNode>,
|
||||
pub(crate) root: AstNodeId,
|
||||
}
|
||||
|
||||
pub type AstNodeId = id_arena::Id<AstNode>;
|
||||
pub type ExpBox = Box<Expression>;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum AstNode {
|
||||
pub enum Expression {
|
||||
Const(f64),
|
||||
Dice(DiceFormula),
|
||||
Neg(AstNodeId),
|
||||
Add(AstNodeId, AstNodeId),
|
||||
Sub(AstNodeId, AstNodeId),
|
||||
Mul(AstNodeId, AstNodeId),
|
||||
Div(AstNodeId, AstNodeId),
|
||||
Neg(ExpBox),
|
||||
Add(ExpBox, ExpBox),
|
||||
Sub(ExpBox, ExpBox),
|
||||
Mul(ExpBox, ExpBox),
|
||||
Div(ExpBox, ExpBox),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct DiceFormula {
|
||||
pub(crate) count: Option<AstNodeId>,
|
||||
pub(crate) size: AstNodeId,
|
||||
pub(crate) kh: Option<AstNodeId>,
|
||||
pub(crate) kl: Option<AstNodeId>,
|
||||
pub(crate) dh: Option<AstNodeId>,
|
||||
pub(crate) dl: Option<AstNodeId>,
|
||||
pub(crate) count: Option<ExpBox>,
|
||||
pub(crate) size: ExpBox,
|
||||
pub(crate) kh: Option<ExpBox>,
|
||||
pub(crate) kl: Option<ExpBox>,
|
||||
pub(crate) dh: Option<ExpBox>,
|
||||
pub(crate) dl: Option<ExpBox>,
|
||||
pub(crate) x: bool,
|
||||
}
|
||||
|
||||
pub enum CompareFragment {
|
||||
Eq(ExpBox),
|
||||
Gt(ExpBox),
|
||||
Gte(ExpBox),
|
||||
Lt(ExpBox),
|
||||
Lte(ExpBox),
|
||||
}
|
||||
|
||||
const DICE_POOL_LIMIT: usize = 100_000;
|
||||
|
||||
impl Expression {
|
||||
pub fn evaluate(&self, mut rng: impl Rng) -> anyhow::Result<String> {
|
||||
pub fn collect_evaluation(&self, mut rng: impl Rng) -> anyhow::Result<String> {
|
||||
let mut result = String::new();
|
||||
let total = self.node_evaluate(&mut rng, self.root, &mut result, 100)?;
|
||||
let total = self.evaluate(&mut rng, &mut result, 100)?;
|
||||
Ok(format!("**{total}** = {result}"))
|
||||
}
|
||||
|
||||
pub fn average(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
|
||||
if let Ok(avg) = self.node_average(self.root) {
|
||||
if let Ok(avg) = self.avg() {
|
||||
return Ok(avg);
|
||||
}
|
||||
let mut average = self.node_sample(rng, self.root)?;
|
||||
let mut average = self.sample(rng)?;
|
||||
for idx in 1..3000 {
|
||||
average =
|
||||
(average * idx as f64 + self.node_sample(rng, self.root)?) / (idx as f64 + 1f64);
|
||||
(average * idx as f64 + self.sample(rng)?) / (idx as f64 + 1f64);
|
||||
}
|
||||
Ok(average)
|
||||
}
|
||||
|
||||
fn node_evaluate(
|
||||
fn evaluate(
|
||||
&self,
|
||||
rng: &mut impl Rng,
|
||||
node_id: AstNodeId,
|
||||
output: &mut String,
|
||||
outer_precedence: i64,
|
||||
) -> anyhow::Result<f64> {
|
||||
use AstNode::*;
|
||||
let node = &self.arena[node_id];
|
||||
use Expression::*;
|
||||
let node = self;
|
||||
let precedence = node.precedence();
|
||||
let needs_parens = precedence > outer_precedence;
|
||||
if needs_parens {
|
||||
|
|
@ -83,7 +83,7 @@ impl Expression {
|
|||
x,
|
||||
}) => {
|
||||
let count = if let Some(count_node) = count_node {
|
||||
self.node_evaluate(rng, *count_node, output, precedence)? as usize
|
||||
count_node.evaluate(rng, output, precedence)? as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
|
@ -91,7 +91,7 @@ impl Expression {
|
|||
bail!("Too many dice.")
|
||||
};
|
||||
output.push_str("d");
|
||||
let size = self.node_evaluate(rng, *size_node, output, precedence)? as i64;
|
||||
let size = size_node.evaluate(rng, output, precedence)? as i64;
|
||||
if size < 1 {
|
||||
bail!("Invalid die size.")
|
||||
}
|
||||
|
|
@ -114,27 +114,27 @@ impl Expression {
|
|||
}
|
||||
}
|
||||
|
||||
let kh = if let Some(id) = kh {
|
||||
let kh = if let Some(child) = kh {
|
||||
output.push_str("kh");
|
||||
Some(self.node_evaluate(rng, *id, output, precedence)? as usize)
|
||||
Some(child.evaluate(rng, output, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let kl = if let Some(id) = kl {
|
||||
let kl = if let Some(child) = kl {
|
||||
output.push_str("kl");
|
||||
Some(self.node_evaluate(rng, *id, output, precedence)? as usize)
|
||||
Some(child.evaluate(rng, output, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dh = if let Some(id) = dh {
|
||||
let dh = if let Some(child) = dh {
|
||||
output.push_str("dh");
|
||||
Some(self.node_evaluate(rng, *id, output, precedence)? as usize)
|
||||
Some(child.evaluate(rng, output, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let dl = if let Some(id) = dl {
|
||||
let dl = if let Some(child) = dl {
|
||||
output.push_str("dl");
|
||||
Some(self.node_evaluate(rng, *id, output, precedence)? as usize)
|
||||
Some(child.evaluate(rng, output, precedence)? as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -201,31 +201,31 @@ impl Expression {
|
|||
}
|
||||
Neg(inner_node) => {
|
||||
output.push_str("-");
|
||||
let inner = self.node_evaluate(rng, *inner_node, output, precedence)?;
|
||||
let inner = inner_node.evaluate(rng, output, precedence)?;
|
||||
-inner
|
||||
}
|
||||
Add(lhs_node, rhs_node) => {
|
||||
let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, output, precedence)?;
|
||||
output.push_str(" + ");
|
||||
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?;
|
||||
let rhs = rhs_node.evaluate(rng, output, precedence)?;
|
||||
lhs + rhs
|
||||
}
|
||||
Sub(lhs_node, rhs_node) => {
|
||||
let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, output, precedence)?;
|
||||
output.push_str(" - ");
|
||||
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?;
|
||||
let rhs = rhs_node.evaluate(rng, output, precedence)?;
|
||||
lhs - rhs
|
||||
}
|
||||
Mul(lhs_node, rhs_node) => {
|
||||
let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, output, precedence)?;
|
||||
output.push_str(" × ");
|
||||
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?;
|
||||
let rhs = rhs_node.evaluate(rng, output, precedence)?;
|
||||
lhs * rhs
|
||||
}
|
||||
Div(lhs_node, rhs_node) => {
|
||||
let lhs = self.node_evaluate(rng, *lhs_node, output, precedence)?;
|
||||
let lhs = lhs_node.evaluate(rng, output, precedence)?;
|
||||
output.push_str(" ÷ ");
|
||||
let rhs = self.node_evaluate(rng, *rhs_node, output, precedence)?;
|
||||
let rhs = rhs_node.evaluate(rng, output, precedence)?;
|
||||
lhs / rhs
|
||||
}
|
||||
};
|
||||
|
|
@ -235,11 +235,11 @@ impl Expression {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
fn node_sample(&self, rng: &mut impl Rng, node_id: AstNodeId) -> anyhow::Result<f64> {
|
||||
let node = &self.arena[node_id];
|
||||
fn sample(&self, rng: &mut impl Rng) -> anyhow::Result<f64> {
|
||||
let node = self;
|
||||
let result = match node {
|
||||
AstNode::Const(x) => *x,
|
||||
AstNode::Dice(DiceFormula {
|
||||
Expression::Const(x) => *x,
|
||||
Expression::Dice(DiceFormula {
|
||||
count,
|
||||
size,
|
||||
kh,
|
||||
|
|
@ -249,14 +249,14 @@ impl Expression {
|
|||
x,
|
||||
}) => {
|
||||
let count = if let Some(count) = count {
|
||||
self.node_sample(rng, *count)? as usize
|
||||
count.sample(rng)? as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
if count > DICE_POOL_LIMIT {
|
||||
bail!("Too many dice.")
|
||||
};
|
||||
let size = self.node_sample(rng, *size)? as i64;
|
||||
let size = size.sample(rng)? as i64;
|
||||
if size < 1 {
|
||||
bail!("Invalid die size.")
|
||||
}
|
||||
|
|
@ -279,23 +279,23 @@ impl Expression {
|
|||
}
|
||||
rolls.sort();
|
||||
|
||||
let kh = if let Some(id) = kh {
|
||||
self.node_sample(rng, *id)? as usize
|
||||
let kh = if let Some(child) = kh {
|
||||
child.sample(rng)? as usize
|
||||
} else {
|
||||
rolls.len()
|
||||
};
|
||||
let kl = if let Some(id) = kl {
|
||||
self.node_sample(rng, *id)? as usize
|
||||
let kl = if let Some(child) = kl {
|
||||
child.sample(rng)? as usize
|
||||
} else {
|
||||
rolls.len()
|
||||
};
|
||||
let dh = if let Some(id) = dh {
|
||||
self.node_sample(rng, *id)? as usize
|
||||
let dh = if let Some(child) = dh {
|
||||
child.sample(rng)? as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let dl = if let Some(id) = dl {
|
||||
self.node_sample(rng, *id)? as usize
|
||||
let dl = if let Some(child) = dl {
|
||||
child.sample(rng)? as usize
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
|
@ -308,20 +308,20 @@ impl Expression {
|
|||
.map(|x| x as f64)
|
||||
.sum::<f64>()
|
||||
}
|
||||
AstNode::Neg(id) => -self.node_sample(rng, *id)?,
|
||||
AstNode::Add(lhs, rhs) => self.node_sample(rng, *lhs)? + self.node_sample(rng, *rhs)?,
|
||||
AstNode::Sub(lhs, rhs) => self.node_sample(rng, *lhs)? - self.node_sample(rng, *rhs)?,
|
||||
AstNode::Mul(lhs, rhs) => self.node_sample(rng, *lhs)? * self.node_sample(rng, *rhs)?,
|
||||
AstNode::Div(lhs, rhs) => self.node_sample(rng, *lhs)? / self.node_sample(rng, *rhs)?,
|
||||
Expression::Neg(id) => -id.sample(rng)?,
|
||||
Expression::Add(lhs, rhs) => lhs.sample(rng)? + rhs.sample(rng)?,
|
||||
Expression::Sub(lhs, rhs) => lhs.sample(rng)? - rhs.sample(rng)?,
|
||||
Expression::Mul(lhs, rhs) => lhs.sample(rng)? * rhs.sample(rng)?,
|
||||
Expression::Div(lhs, rhs) => lhs.sample(rng)? / rhs.sample(rng)?,
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn node_average(&self, node_id: AstNodeId) -> anyhow::Result<f64> {
|
||||
let node = &self.arena[node_id];
|
||||
fn avg(&self) -> anyhow::Result<f64> {
|
||||
let node = self;
|
||||
let result = match node {
|
||||
AstNode::Const(x) => *x,
|
||||
AstNode::Dice(DiceFormula {
|
||||
Expression::Const(x) => *x,
|
||||
Expression::Dice(DiceFormula {
|
||||
count,
|
||||
size,
|
||||
kh,
|
||||
|
|
@ -331,14 +331,14 @@ impl Expression {
|
|||
x,
|
||||
}) => {
|
||||
let count = if let Some(count) = count {
|
||||
self.node_average(*count)? as usize
|
||||
count.avg()? as usize
|
||||
} else {
|
||||
1
|
||||
};
|
||||
if count > 1_000_000 {
|
||||
bail!("Too many dice.")
|
||||
};
|
||||
let size = self.node_average(*size)? as i64;
|
||||
let size = size.avg()? as i64;
|
||||
if size < 1 {
|
||||
bail!("Invalid die size.")
|
||||
}
|
||||
|
|
@ -361,19 +361,19 @@ impl Expression {
|
|||
|
||||
count as f64 * (size as f64 + 1f64) * 0.5
|
||||
}
|
||||
AstNode::Neg(id) => -self.node_average(*id)?,
|
||||
AstNode::Add(lhs, rhs) => self.node_average(*lhs)? + self.node_average(*rhs)?,
|
||||
AstNode::Sub(lhs, rhs) => self.node_average(*lhs)? - self.node_average(*rhs)?,
|
||||
AstNode::Mul(lhs, rhs) => self.node_average(*lhs)? * self.node_average(*rhs)?,
|
||||
AstNode::Div(lhs, rhs) => self.node_average(*lhs)? / self.node_average(*rhs)?,
|
||||
Expression::Neg(child) => -child.avg()?,
|
||||
Expression::Add(lhs, rhs) => lhs.avg()? + rhs.avg()?,
|
||||
Expression::Sub(lhs, rhs) => lhs.avg()? - rhs.avg()?,
|
||||
Expression::Mul(lhs, rhs) => lhs.avg()? * rhs.avg()?,
|
||||
Expression::Div(lhs, rhs) => lhs.avg()? / rhs.avg()?,
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl AstNode {
|
||||
impl Expression {
|
||||
fn precedence(&self) -> i64 {
|
||||
use AstNode::*;
|
||||
use Expression::*;
|
||||
match self {
|
||||
Const(_) => 1,
|
||||
Dice { .. } => 2,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue