Wrote parser and added tests for it.

Refactored tests a little.
This commit is contained in:
vorboyvo 2025-09-23 14:51:26 -04:00
parent 6ae8385f7d
commit 6d79b7a7fa
4 changed files with 138 additions and 61 deletions

32
Cargo.lock generated
View file

@ -1,32 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "assert_approx_eq"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd"
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "code"
version = "0.1.0"
dependencies = [
"assert_approx_eq",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]

View file

@ -5,4 +5,5 @@ edition = "2024"
[dependencies] [dependencies]
assert_approx_eq = "1.1.0" assert_approx_eq = "1.1.0"
nom = "8.0.0"
num-traits = "0.2" num-traits = "0.2"

View file

@ -1,5 +1,7 @@
use num_traits::Float; use num_traits::Float;
mod parser;
/* /*
* NOTES * NOTES
* - each alternative should appear only once in a tree. need to figure out a * - each alternative should appear only once in a tree. need to figure out a
@ -7,22 +9,25 @@ use num_traits::Float;
* - looks like this is like quite difficult to do through the type system * - looks like this is like quite difficult to do through the type system
*/ */
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq, Debug)]
struct Alternative<T: Eq> { pub struct Alternative<T: Eq> {
name: T name: T
} }
struct Edge<T: Eq, U: Float> { #[derive(PartialEq, Eq, Debug)]
pub struct Edge<T: Eq, U: Float> {
weight: U, weight: U,
destination: Tree<T, U> destination: Tree<T, U>
} }
enum Vertex<T: Eq, U: Float> { #[derive(PartialEq, Eq, Debug)]
pub enum Vertex<T: Eq, U: Float> {
NonTerminal(Box<Edge<T, U>>, Box<Edge<T, U>>), NonTerminal(Box<Edge<T, U>>, Box<Edge<T, U>>),
Terminal(Alternative<T>), Terminal(Alternative<T>),
} }
struct Tree<T: Eq, U: Float> { #[derive(PartialEq, Eq, Debug)]
pub struct Tree<T: Eq, U: Float> {
root: Vertex<T, U> root: Vertex<T, U>
} }
@ -84,11 +89,13 @@ mod tests {
use super::*; use super::*;
use assert_approx_eq::assert_approx_eq; use assert_approx_eq::assert_approx_eq;
// A test for the simplest symmetric case fn simple_symmetric_tree() -> Tree<String, f64> {
#[test] let a = Alternative {
fn choice_probability_test_1() { name: "A".to_owned()
let a = Alternative {name: "A"}; };
let b = Alternative {name: "B"}; let b = Alternative {
name: "B".to_owned()
};
let edge_a = Edge { let edge_a = Edge {
weight: 1.0, weight: 1.0,
destination: Tree {root: Vertex::Terminal(a)} destination: Tree {root: Vertex::Terminal(a)}
@ -98,28 +105,38 @@ mod tests {
destination: Tree {root: Vertex::Terminal(b)} destination: Tree {root: Vertex::Terminal(b)}
}; };
let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b)); let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b));
let tree = Tree {root: root}; Tree {root: root}
assert_eq!(tree.choice_probability(&"A"), 0.5);
} }
// A test for the simplest asymmetric case fn simple_asymmetric_tree() -> Tree<String, f64> {
#[test] let a = Alternative {
fn choice_probability_test_2() { name: "A".to_owned()
let a = Alternative {name: "A"}; };
let b = Alternative {name: "B"}; let b = Alternative {
let edge_a = Edge {weight: 3.0, destination: Tree {root: Vertex::Terminal(a)}}; name: "B".to_owned()
let edge_b = Edge {weight: 1.0, destination: Tree {root: Vertex::Terminal(b)}}; };
let edge_a = Edge {
weight: 3.0,
destination: Tree {root: Vertex::Terminal(a)}
};
let edge_b = Edge {
weight: 1.0,
destination: Tree {root: Vertex::Terminal(b)}
};
let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b)); let root = Vertex::NonTerminal(Box::new(edge_a), Box::new(edge_b));
let tree = Tree {root: root}; Tree {root: root}
assert_eq!(tree.choice_probability(&"A"), 0.75);
} }
// A test for depth higher than 1 fn complex_tree() -> Tree<String, f64> {
#[test] let a = Alternative {
fn choice_probability_test_3() { name: "A".to_owned()
let a = Alternative {name: "A"}; };
let b = Alternative {name: "B"}; let b = Alternative {
let c = Alternative {name: "C"}; name: "B".to_owned()
};
let c = Alternative {
name: "C".to_owned()
};
let edge_a = Edge { let edge_a = Edge {
weight: 2.5, weight: 2.5,
destination: Tree {root: Vertex::Terminal(a)} destination: Tree {root: Vertex::Terminal(a)}
@ -139,10 +156,62 @@ mod tests {
destination: Tree {root: Vertex::Terminal(c)} destination: Tree {root: Vertex::Terminal(c)}
}; };
let root = Vertex::NonTerminal(Box::new(edge_ab), Box::new(edge_c)); let root = Vertex::NonTerminal(Box::new(edge_ab), Box::new(edge_c));
let tree = Tree {root: root}; Tree {root: root}
}
// A test for the simplest symmetric case
#[test]
fn choice_probability_test_1() {
assert_eq!(simple_symmetric_tree().choice_probability(&("A".to_owned())), 0.5);
}
// A test for the simplest asymmetric case
#[test]
fn choice_probability_test_2() {
assert_eq!(simple_asymmetric_tree().choice_probability(&("A".to_owned())), 0.75);
}
// A test for depth higher than 1
#[test]
fn choice_probability_test_3() {
assert_approx_eq!( assert_approx_eq!(
tree.choice_probability(&"A"), complex_tree().choice_probability(&("A".to_owned())),
(2.5/(2.5+1.0))*((2.5+1.0+0.5)/(2.5+1.0+0.5+1.0)) (2.5/(2.5+1.0))*((2.5+1.0+0.5)/(2.5+1.0+0.5+1.0))
); );
} }
// A test for parsing the simplest symmetric case
#[test]
fn parser_test_1() {
assert_eq!(
{
let (_, b) = parser::subtree("([1.0]A[1.0]B)").unwrap();
b
},
simple_symmetric_tree()
)
}
#[test]
fn parser_test_2() {
assert_eq!(
{
let (_, b) = parser::subtree("([3.0]A[1.0]B)").unwrap();
b
},
simple_asymmetric_tree()
)
}
#[test]
fn parser_test_3() {
assert_eq!(
{
let (_, b) = parser::subtree("([0.5]([2.5]A[1.0]B)[1.0]C)").unwrap();
b
},
complex_tree()
)
}
} }

39
src/parser.rs Normal file
View file

@ -0,0 +1,39 @@
/*
* WHAT WE ARE PARSING:
*
*/
use nom::{branch::alt, bytes::complete::take_while1, character::char, combinator::map, error::Error, number::complete::double, sequence::{delimited, pair}, AsChar, IResult, Parser};
use crate::{Alternative, Edge, Tree, Vertex};
fn weighted_edge(input: &str) -> IResult<&str, Edge<String, f64>> {
let weight = delimited(
char::<&str, Error<&str>>('['),
double,
char(']')
);
let alternative = map(
take_while1::<_, &str, Error<&str>>(AsChar::is_alpha),
|a| Tree::<String, f64> {
root: Vertex::Terminal(Alternative{name: a.to_string()})
}
);
map(
pair(weight, alt((alternative, subtree))),
|(a, b)| Edge{
weight: a,
destination: b
}
).parse(input)
}
pub fn subtree(input: &str) -> IResult<&str, Tree<String, f64>> {
let inner = map(
pair(weighted_edge, weighted_edge),
|(a, b)| Tree{
root: Vertex::NonTerminal(Box::new(a), Box::new(b))
}
);
delimited(char('('), inner, char(')')).parse(input)
}