1use crate::{
7 camel::{Camel, Dice, Race},
8 fraction::Fraction,
9 tree::{LeafVisitor, Tree},
10};
11use std::{collections::HashMap, iter::Iterator, ops::Index};
12
13pub fn project(race: &Race, dice: &Dice) -> Chances {
17 let mut tree = Tree::singleton(race.clone());
18 tree.expand(dice);
19
20 let mut counter: LeafCounter = Default::default();
21 tree.visit_leaves(&mut counter);
22
23 counter.chances()
24}
25
26pub struct Chances {
30 pub winner: Distribution,
32 pub runner_up: Distribution,
34 pub loser: Distribution,
36}
37
38pub struct Distribution {
40 distribution: HashMap<Camel, Fraction>,
41 default: Fraction,
42}
43
44impl Distribution {
45 pub fn values(&self) -> impl Iterator<Item = (&Camel, &Fraction)> + '_ {
49 self.distribution.iter()
50 }
51}
52
53impl From<HashMap<Camel, Fraction>> for Distribution {
54 fn from(distribution: HashMap<Camel, Fraction>) -> Self {
55 Self {
56 distribution,
57 default: Fraction::default(),
58 }
59 }
60}
61
62impl Index<&Camel> for Distribution {
63 type Output = Fraction;
64
65 fn index(&self, camel: &Camel) -> &Self::Output {
66 self.distribution.get(camel).unwrap_or(&self.default)
67 }
68}
69
70struct LeafCounter {
71 total: usize,
72 winner: HashMap<Camel, usize>,
73 runner_up: HashMap<Camel, usize>,
74 loser: HashMap<Camel, usize>,
75}
76
77impl LeafCounter {
78 fn chances(&self) -> Chances {
79 let winner: HashMap<Camel, Fraction> = self
80 .winner
81 .iter()
82 .map(|(camel, count)| (*camel, Fraction::new(*count as i64, self.total as u64)))
83 .collect();
84 let runner_up: HashMap<Camel, Fraction> = self
85 .runner_up
86 .iter()
87 .map(|(camel, count)| (*camel, Fraction::new(*count as i64, self.total as u64)))
88 .collect();
89 let loser: HashMap<Camel, Fraction> = self
90 .loser
91 .iter()
92 .map(|(camel, count)| (*camel, Fraction::new(*count as i64, self.total as u64)))
93 .collect();
94 Chances {
95 winner: Distribution::from(winner),
96 runner_up: Distribution::from(runner_up),
97 loser: Distribution::from(loser),
98 }
99 }
100}
101
102impl Default for LeafCounter {
103 fn default() -> Self {
104 Self {
105 total: 0,
106 winner: HashMap::new(),
107 runner_up: HashMap::new(),
108 loser: HashMap::new(),
109 }
110 }
111}
112
113impl LeafVisitor for LeafCounter {
114 fn visit(&mut self, race: &Race) {
115 if let Some(winner) = race.winner() {
116 *self.winner.entry(winner).or_insert(0) += 1;
117 };
118 if let Some(runner_up) = race.runner_up() {
119 *self.runner_up.entry(runner_up).or_insert(0) += 1;
120 };
121 if let Some(loser) = race.loser() {
122 *self.loser.entry(loser).or_insert(0) += 1;
123 };
124 self.total += 1;
125 }
126}
127
128#[cfg(test)]
129mod test {
130 use super::*;
131
132 #[test]
133 fn should_have_a_clear_winner() {
134 let race = "r,y".parse::<Race>().expect("to parse");
135 let dice = "r".parse::<Dice>().expect("to parse");
136 let chances = project(&race, &dice);
137
138 assert_eq!(chances.winner[&Camel::Red], Fraction::one());
139 }
140
141 #[test]
142 fn should_determine_chances() {
143 let race = "r,,y".parse::<Race>().expect("to parse");
144 let dice = "r".parse::<Dice>().expect("to parse");
145 let chances = project(&race, &dice);
146
147 assert_eq!(chances.winner[&Camel::Red], Fraction::new(2, 3));
148 assert_eq!(chances.winner[&Camel::Yellow], Fraction::new(1, 3));
149 }
150}