use crate::common::PairScore::*;
use crate::common::{ElectionError, PairScore};
use crate::outcome::GenericOutcome;
use crate::prng::Prng;
use crate::tally::{Tally, VoteMatrix};
use std::cmp::max;
use std::collections::HashMap;
pub struct Minimax<'a> {
tally: &'a VoteMatrix,
pair_score: PairScore,
seed: u32,
}
impl<'a> Minimax<'a> {
pub fn new(tally: &'a VoteMatrix) -> Self {
Self {
tally,
pair_score: Winning,
seed: 21,
}
}
pub fn pair_score(&mut self, pair_score: PairScore) -> &mut Self {
self.pair_score = pair_score;
self
}
pub fn seed(&mut self, seed: u32) -> &mut Self {
self.seed = seed;
self
}
pub fn run(&self) -> Result<GenericOutcome<'a>, ElectionError> {
let mut rnd = Prng::new(self.seed);
let mut greatest_defeats: HashMap<u32, i64> = match self.pair_score {
Winning => (0..self.tally.get_candidates()).map(|e| (e, 0)).collect(),
_ => HashMap::new(),
};
for ((_runner, opponent), (uwin, uopp)) in self.tally.pairs() {
let win = if uwin <= i64::MAX as u64 {
uwin as i64
} else {
return Err(ElectionError::ArithmeticOverflow);
};
let opp = if uopp <= i64::MAX as u64 {
uopp as i64
} else {
return Err(ElectionError::ArithmeticOverflow);
};
let v = match self.pair_score {
Winning => {
if win > opp {
win
} else {
0
}
}
Opposition => win,
Margin => win - opp,
};
greatest_defeats
.entry(opponent)
.and_modify(|defeat| *defeat = max(*defeat, v))
.or_insert(v);
}
let minimax_defeat = greatest_defeats.values().min();
let mut winners: Vec<u32> = greatest_defeats
.iter()
.filter_map(|(c, &s)| {
minimax_defeat.and_then(|&ms| if s == ms { Some(*c) } else { None })
})
.collect();
if !winners.is_empty() {
winners.sort_unstable();
let winner = rnd.only_or_random(&winners).unwrap();
Ok(GenericOutcome::new(
Some(winner),
None,
self.tally.get_candidates(),
rnd.sealed(),
self.tally.get_meta(),
))
} else {
Err(ElectionError::NotEnoughCandidates)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Outcome;
#[test]
fn minimax_basic() {
use crate::PairScore::*;
let x: VoteMatrix = VoteMatrix::with_vector_bycol(
&vec![58, 58, 58, 42, 32, 32, 42, 68, 17, 42, 68, 83],
);
for ps in vec![Winning, Margin, Opposition] {
assert_eq!(
Minimax::new(&x)
.pair_score(ps)
.run()
.unwrap()
.winner()
.unwrap(),
1
);
}
}
#[test]
fn overflows() {
use crate::ElectionError;
let x: VoteMatrix = VoteMatrix::with_vector_bycol(&vec![u64::max_value(), 1]);
match Minimax::new(&x).run().unwrap_err() {
ElectionError::ArithmeticOverflow => (),
_ => unreachable!(),
}
}
#[test]
fn no_cands() {
use crate::ElectionError;
let x: VoteMatrix = VoteMatrix::with_vector_bycol(&vec![]);
match Minimax::new(&x).run().unwrap_err() {
ElectionError::NotEnoughCandidates => (),
_ => unreachable!(),
}
}
}