#![warn(missing_docs)]
#![feature(btree_cursors)]
mod graph;
mod pairwise;
#[cfg(test)]
mod test;
use std::collections::{BTreeSet, HashSet};
use itertools::Itertools as _;
pub fn tally<B: AsRef<[u16]>>(ballots: &[B], candidates: u16) -> Result<BTreeSet<u16>, Error> {
check_ballots(ballots, candidates)?;
match candidates {
0 => return Ok(BTreeSet::from([])),
1 => return Ok(BTreeSet::from([0])),
_ => {}
}
let pairwise_results = pairwise::tabulate_pairwise_results(ballots, candidates);
let mut graphs = HashSet::from([graph::AcyclicGraph::new(candidates)]);
println!("{pairwise_results:?}");
for pairings in pairwise_results.into_values().rev() {
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();
}
Ok(graphs.iter().flat_map(|graph| graph.roots()).collect())
}
fn check_ballots<B: AsRef<[u16]>>(ballots: &[B], candidates: u16) -> Result<(), Error> {
for ballot in ballots {
let ballot = ballot.as_ref();
let mut selections = rangemap::RangeSet::new();
for v in ballot {
if *v >= candidates {
return Err(Error::InvalidCandidate);
}
let v = usize::from(*v);
if selections.contains(&v) {
return Err(Error::InvalidBallot);
} else {
selections.insert(v..v + 1);
}
}
}
Ok(())
}
#[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,
}