#![warn(missing_docs)]
#![feature(btree_cursors)]
mod graph;
mod pairwise;
#[cfg(test)]
mod test;
use std::collections::{BTreeMap, BTreeSet, HashSet};
use itertools::Itertools as _;
#[derive(Debug)]
pub struct TabulatedData {
table: BTreeMap<usize, BTreeSet<(u16, u16)>>,
candidates: u16,
}
impl TabulatedData {
pub fn from_ballots<B: AsRef<[u16]>>(
ballots: impl IntoIterator<Item = B> + Copy,
candidates: u16,
) -> Result<Self, Error> {
Ok(Self {
table: pairwise::tabulate_pairwise_results(ballots, candidates)?,
candidates,
})
}
pub fn tally(&self) -> BTreeSet<u16> {
let mut graphs = HashSet::from([graph::AcyclicGraph::new(self.candidates)]);
for pairings in self.pairwise_results() {
let possible_match_orders = pairings.iter().copied().permutations(pairings.len());
graphs = graphs
.into_iter()
.cartesian_product(possible_match_orders)
.map(|(mut graph, matches)| {
for (winner, loser) in matches {
graph.try_add_edge(winner, loser);
}
graph
})
.collect();
}
graphs.iter().flat_map(|graph| graph.roots()).collect()
}
pub fn pairwise_results(&self) -> impl Iterator<Item = &BTreeSet<(u16, u16)>> {
self.table.values().rev()
}
}
pub fn tally<B: AsRef<[u16]>>(ballots: &[B], candidates: u16) -> Result<BTreeSet<u16>, Error> {
TabulatedData::from_ballots(ballots, candidates).map(|d| d.tally())
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
#[error("an invalid ballot was given")]
InvalidBallot,
#[error("an invalid candidate was voted for")]
InvalidCandidate,
}