use self::PairScore::*;
use crate::common::{ElectionError, PairScore};
use crate::outcome::GenericOutcome;
use crate::tally::{Tally, VoteMatrix};
use std::collections::HashMap;
pub struct Schulze<'a> {
tally: &'a VoteMatrix,
pair_score: PairScore,
}
fn strongest_paths<S: ::std::hash::BuildHasher>(n: u32, x: &mut HashMap<(u32, u32), u64, S>) {
use std::cmp::{max, min};
for i in 0..n {
for j in 0..n {
for k in 0..n {
if i != j && i != k && j != k {
if let Some(&pji) = x.get(&(j, i)) {
if let Some(&pik) = x.get(&(i, k)) {
let alt = min(pji, pik);
x.entry((j, k))
.and_modify(|e| *e = max(*e, alt))
.or_insert(alt);
}
}
}
}
}
}
for i in 1..n {
for j in 0..i {
if let Some(&vij) = x.get(&(i, j)) {
if let Some(&vji) = x.get(&(j, i)) {
if vij >= vji {
x.remove(&(j, i));
}
if vji >= vij {
x.remove(&(i, j));
}
}
}
}
}
}
impl<'a> Schulze<'a> {
pub fn new(tally: &'a VoteMatrix) -> Self {
Self {
tally,
pair_score: Winning,
}
}
pub fn pair_score(&mut self, pair_score: PairScore) -> &mut Self {
self.pair_score = pair_score;
self
}
pub fn run(&self) -> Result<GenericOutcome<'a>, ElectionError> {
if self.tally.get_candidates() == 0 {
return Err(ElectionError::NotEnoughCandidates);
}
let mut paths: HashMap<(u32, u32), u64> = self
.tally
.pairs()
.filter_map(|((runner, opponent), (win, opposition))| {
let score: u64 = match (win > opposition, self.pair_score) {
(true, Margin) => win - opposition,
(true, Winning) => win,
(_, Opposition) => win,
_ => 0,
};
if score > 0 {
Some(((runner, opponent), score))
} else {
None
}
})
.collect();
strongest_paths(self.tally.get_candidates(), &mut paths);
let mut is_winner: HashMap<u32, bool> = HashMap::new();
for (&(winner, looser), _) in paths.iter() {
is_winner.entry(winner).or_insert(true);
is_winner.insert(looser, false);
}
let mut winner_iter = is_winner
.iter()
.filter_map(|(&c, &w)| if w { Some(c) } else { None });
if let Some(final_winner) = winner_iter.next() {
if winner_iter.next().is_none() {
Ok(GenericOutcome::new(
Some(final_winner),
None,
self.tally.get_candidates(),
true,
self.tally.get_meta(),
))
} else {
Err(ElectionError::DegeneratedElectionGraph)
}
} else {
Err(ElectionError::DegeneratedElectionGraph)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ElectionError;
use crate::Outcome;
#[test]
fn schulze_basic() {
let x = VoteMatrix::with_vector_bycol(
&vec![58, 58, 58, 42, 32, 32, 42, 68, 17, 42, 68, 83],
);
assert_eq!(Schulze::new(&x).run().unwrap().winner().unwrap(), 1);
}
#[test]
fn schulze_wiki() {
let x = VoteMatrix::with_vector_bycol(&vec![
25, 0, 0, 23, 0, 29, 0, 27, 26, 0, 28, 0, 30, 33, 0, 31, 0, 0, 24, 0,
]);
assert_eq!(Schulze::new(&x).run().unwrap().winner().unwrap(), 4);
}
#[test]
fn schulze_no_wins() {
let x = VoteMatrix::with_vector_bycol(&vec![3, 3, 3, 3, 3, 3]);
match Schulze::new(&x).run().unwrap_err() {
ElectionError::DegeneratedElectionGraph => (),
_ => unreachable!(),
}
}
#[test]
fn schulze_no_cands() {
let x: VoteMatrix = VoteMatrix::with_vector_bycol(&vec![]);
match Schulze::new(&x).run().unwrap_err() {
ElectionError::NotEnoughCandidates => (),
_ => unreachable!(),
}
}
}