use crate::common::BordaKind;
use crate::outcome::GenericOutcome;
use crate::prng::Prng;
use crate::tally::Tally;
use crate::ElectionError;
use crate::VoteTree;
use std::collections::HashSet;
use BordaKind::*;
pub struct Baldwin<'a> {
tally: &'a VoteTree,
borda_kind: BordaKind,
withdraw: HashSet<u32>,
max_rank: Option<u32>,
seed: u32,
}
impl<'a> Baldwin<'a> {
pub fn new(tally: &'a VoteTree) -> Self {
let mut withdraw: HashSet<u32> = HashSet::new();
tally.map_withdrawn(|c| {
withdraw.insert(c);
});
Self {
tally,
borda_kind: Borda1,
withdraw,
max_rank: None,
seed: 21,
}
}
pub fn seed(&mut self, seed: u32) -> &mut Self {
self.seed = seed;
self
}
pub fn kind(&mut self, kind: BordaKind) -> &mut Self {
self.borda_kind = kind;
self
}
pub fn max_rank(&mut self, max_rank: u32) -> &mut Self {
self.max_rank = Some(max_rank);
self
}
pub fn withdraw<I>(&mut self, withdraw: I) -> &mut Self
where
I: IntoIterator<Item = u32>,
{
self.withdraw.clear();
for e in withdraw.into_iter() {
self.withdraw.insert(e);
}
self
}
pub fn run(&self) -> Result<GenericOutcome<'a>, ElectionError> {
let mut rnd = Prng::new(self.seed);
use crate::methods::borda::Borda;
let mut borda = Borda::new(self.tally);
borda.kind(self.borda_kind);
if let Some(mr) = self.max_rank {
borda.max_rank(mr);
};
let mut eliminated: HashSet<u32> = HashSet::new();
for e in self.withdraw.iter() {
eliminated.insert(*e);
}
let winner = loop {
let scores = borda.withdraw(eliminated.iter().cloned()).scores()?;
if scores.is_empty() {
return Err(ElectionError::NotEnoughCandidates);
}
if scores.len() == 1 {
break *scores.keys().next().unwrap();
}
let min = scores.values().min().unwrap();
let mut loosers: Vec<u32> = scores
.iter()
.filter_map(|(c, s)| if s == min { Some(*c) } else { None })
.collect();
loosers.sort_unstable();
let looser = rnd.only_or_random(&loosers).unwrap();
if scores.len() == 2 {
break *scores.keys().find(|&&c| c != looser).unwrap();
}
eliminated.insert(looser);
};
Ok(GenericOutcome::new(
Some(winner),
self.withdraw.iter().cloned(),
self.tally.get_candidates(),
rnd.sealed(),
self.tally.get_meta(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::outcome::Outcome;
#[test]
fn demo() {
let tally = Tally::from_blt_file("examples/tennessee.blt").unwrap();
let outcome = Baldwin::new(&tally).run().unwrap();
assert!(outcome.deterministic());
assert_eq!(outcome.winner_name().unwrap(), "Nashville");
}
#[test]
fn with_tie() {
let x = [
(10, vec![0, 1]),
(10, vec![1, 0]),
(5, vec![2, 3]),
(5, vec![3, 2]),
]
.iter()
.collect();
let mut stub = Baldwin::new(&x);
stub.max_rank(1);
stub.seed(97);
let ans = stub.kind(Borda0).run().unwrap();
assert!(!ans.deterministic());
assert_eq!(ans.winner().unwrap(), 1);
stub.seed(99);
assert_eq!(stub.kind(Borda0).run().unwrap().winner().unwrap(), 1);
}
}