use self::BordaKind::*;
use crate::common::{BordaKind, ElectionError};
use crate::outcome::GenericOutcome;
use crate::prng::Prng;
use crate::tally::{Tally, VoteTree};
use std::collections::{HashMap, HashSet};
fn gcd(a: u64, b: u64) -> u64 {
let mut a = a;
let mut b = b;
if b > a {
std::mem::swap(&mut a, &mut b);
}
loop {
let m = a % b;
if m == 0 {
break b;
} else {
a = b;
b = m;
}
}
}
fn lcm(a: u64, b: u64) -> u64 {
a * b / gcd(a, b)
}
fn multi(n: u64) -> Option<u64> {
if n > 42 {
None
} else {
Some((2..=(n + 1)).fold(1, lcm))
}
}
pub struct Borda<'a> {
tally: &'a VoteTree,
kind: BordaKind,
withdraw: HashSet<u32>,
max_rank: Option<u32>,
seed: u32,
}
impl<'a> Borda<'a> {
pub fn new(tally: &'a VoteTree) -> Self {
let mut withdraw: HashSet<u32> = HashSet::new();
tally.map_withdrawn(|c| {
withdraw.insert(c);
});
Self {
tally,
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.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(super) fn scores(&self) -> Result<HashMap<u32, u64>, ElectionError> {
let counts = self.tally.count_ranks(&self.withdraw);
let max_rank = self
.max_rank
.unwrap_or_else(|| *(counts.keys().map(|(_, r)| r).max().unwrap_or(&0)));
let denominator = if let Nauru = self.kind {
match multi(u64::from(max_rank)) {
Some(x) => x,
None => return Err(ElectionError::ArithmeticOverflow),
}
} else {
1
};
let mut scores: HashMap<u32, u64> = HashMap::new();
for (&(cand, rank), &count) in &counts {
if rank <= max_rank {
if let Some(score) = count.checked_mul(match self.kind {
Borda0 => {
if max_rank > 0 {
u64::from(max_rank - rank)
} else {
1
}
}
Borda1 => u64::from(max_rank - rank + 1),
Nauru => denominator / u64::from(rank + 1),
}) {
let val = scores.entry(cand).or_insert(0);
if let Some(nv) = (*val).checked_add(score) {
*val = nv;
} else {
return Err(ElectionError::ArithmeticOverflow);
}
} else {
return Err(ElectionError::ArithmeticOverflow);
}
}
}
Ok(scores)
}
pub fn run(&self) -> Result<GenericOutcome<'a>, ElectionError> {
let mut rnd = Prng::new(self.seed);
let scores = self.scores()?;
let best_score = scores.values().max();
let mut winners: Vec<u32> = scores
.iter()
.filter_map(|(c, &s)| best_score.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),
self.withdraw.iter().cloned(),
self.tally.get_candidates(),
rnd.sealed(),
self.tally.get_meta(),
))
} else {
Err(ElectionError::NotEnoughCandidates)
}
}
}
#[cfg(test)]
mod tests {
use self::BordaKind::*;
use super::*;
use crate::Outcome;
#[test]
fn demo() {
let tally = Tally::from_blt_file("examples/tennessee.blt").unwrap();
let outcome = Borda::new(&tally).run().unwrap();
assert_eq!(outcome.winner_name().unwrap(), "Nashville");
}
#[test]
fn borda_multi() {
fn check(max_rank: u64) {
let mlt = multi(max_rank).unwrap();
for e in 2..=(max_rank + 1) {
assert_eq!(mlt % e, 0);
}
}
for e in 3..11 {
check(e);
}
assert_eq!(multi(0).unwrap(), 1);
assert_eq!(multi(1).unwrap(), 1 * 2);
assert_eq!(multi(2).unwrap(), 1 * 2 * 3);
assert_eq!(multi(3).unwrap(), 1 * 3 * 4);
assert_eq!(multi(4).unwrap(), 1 * 3 * 4 * 5);
assert_eq!(multi(5).unwrap(), 1 * 3 * 5 * 4);
assert!(multi(50).is_none());
}
#[test]
fn borda_simple() {
let x = [
(1, vec![0, 1]),
(2, vec![0, 2]),
(3, vec![1, 0]),
(2, vec![1, 2]),
(2, vec![2, 0]),
(2, vec![2, 1]),
(1, vec![4, 3, 2]),
]
.iter()
.collect();
let mut stub = Borda::new(&x);
stub.seed(97);
assert_eq!(stub.kind(Borda0).run().unwrap().winner().unwrap(), 1);
assert_eq!(stub.kind(Nauru).run().unwrap().winner().unwrap(), 1);
assert_eq!(stub.kind(Borda1).run().unwrap().winner().unwrap(), 2);
assert_eq!(
Borda::new(&x)
.kind(Borda1)
.withdraw(vec![0, 2])
.run()
.unwrap()
.winner()
.unwrap(),
1
);
}
#[test]
fn borda_wiki() {
let x = [
(51, vec![0, 2, 1, 3]),
(5, vec![2, 1, 3, 0]),
(23, vec![1, 2, 3, 0]),
(21, vec![3, 2, 1, 0]),
]
.iter()
.collect();
let mut stub = Borda::new(&x);
stub.seed(97);
assert_eq!(stub.kind(Borda0).run().unwrap().winner().unwrap(), 2);
assert_eq!(stub.kind(Borda1).run().unwrap().winner().unwrap(), 2);
assert_eq!(stub.kind(Nauru).run().unwrap().winner().unwrap(), 0);
}
#[test]
fn borda_empty() {
let x = [].iter().collect();
match Borda::new(&x).run().unwrap_err() {
ElectionError::NotEnoughCandidates => (),
_ => unreachable!(),
}
}
}