wybr 0.0.6

Collection of preferential voting methods
Documentation
//! Election data tallies

pub mod vote_matrix;
pub mod vote_tree;

pub use self::vote_matrix::VoteMatrix;
pub use self::vote_tree::{Transfer, VoteTree};

use std::error::Error;
use std::io;

///VoteToken represents an unit of information parsed from the input file
///Wybr I/O is based on an idea that ballot file parser shall return an iterator of `VoteToken`s,
///and they can be collected into something that is `Tally`, currently `VoteMatrix` or `VoteTree`.
///Finally, either of this is then passed to some vote counting method or other tool.
pub enum VoteToken {
    ///Candidate number declaration; must happen before any `Vote` or `Ballot*`
    DeclareCandidates(u32),
    ///Seats (number of candidates to be elected) declaration; ignored by some methods
    DeclareSeats(u32),
    ///Withdraw certain candidate of a given ID; ignored by some methods
    WithdrawCandidate(u32),
    ///Declare how many times the current ballot was observed; obligatory
    BallotRepeat(u64),
    ///Vote -- priority (the lower the better, `i32::MAX` is reserved) and candidate ID
    Vote(i32, u32),
    ///Declare candidate name; optional
    CandidateName(u32, String),
    ///Declare election name; optional
    ElectionTitle(String),
    ///Finish current ballot; obligatory
    EndBallot,
    ///Expect no more ballot in this iterator; obligatory
    EndBallots,
    ///Read failure
    ReadFailure(VoteReadError),
}

///Errors possible during ballot data parsing
///
///`usize` element marks the line at which error occurred.
pub enum VoteReadError {
    InvalidToken(usize, String),
    CannotParse(usize, String),
    WrongSyntax(usize),
    InvalidValue(usize, String),
    EqualVote,
    InvalidSeats(u32),
    InvalidMeta,
    IOError(io::Error),
}
impl Error for VoteReadError {}
impl std::convert::From<std::io::Error> for VoteReadError {
    fn from(x: std::io::Error) -> Self {
        VoteReadError::IOError(x)
    }
}

use std::fmt;
impl fmt::Debug for VoteReadError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self)
    }
}
impl fmt::Display for VoteReadError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::VoteReadError::*;
        match self {
            InvalidToken(line, token) => {
                write!(f, "Unexpected token >>{}<< on line {}", token, line)
            }
            CannotParse(line, token) => write!(f, "Cannot parse >>{}<< on line {}", token, line),
            WrongSyntax(line) => write!(f, "Wrong syntax on line {}", line),
            InvalidValue(line, token) => write!(f, "Invalid value >>{}<< on line {}", token, line),
            EqualVote => write!(f, "Equal vote in a count that does not support such"),
            InvalidSeats(value) => write!(f, "Invalid seats number ({})", value),
            InvalidMeta => write!(f, "Invalid metadata"),
            IOError(x) => write!(f, "{}", x),
        }
    }
}

use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;

#[derive(Debug, Clone)]
///Structure holding metadata about the election
///
///It is used as source of default values of election parameters (like the number of seats or
///withdrawn candidates), as well as a container of candidate names and other strings.
pub struct Metadata {
    seats: Option<u32>,
    withdrawn: HashSet<u32>,
    candidate_names: HashMap<u32, String>,
    title: Option<String>,
}
impl Metadata {
    pub fn new(
        seats: Option<u32>,
        withdrawn: HashSet<u32>,
        candidate_names: HashMap<u32, String>,
        title: Option<String>,
    ) -> Metadata {
        Metadata {
            seats,
            withdrawn,
            candidate_names,
            title,
        }
    }
    ///Empty metadata constructor, for tests, etc.
    pub fn empty() -> Metadata {
        Metadata::new(None, HashSet::new(), HashMap::new(), None)
    }
    //Translate candidate ID into a string
    pub fn get_name(&self, who: u32) -> Option<&String> {
        self.candidate_names.get(&who)
    }
    pub fn get_seats(&self) -> u32 {
        self.seats.unwrap_or(0)
    }
    pub fn get_title(&self) -> Option<&String> {
        self.title.as_ref()
    }
    pub fn map_withdrawn<F: FnMut(u32)>(&self, mut f: F) {
        for e in self.withdrawn.iter() {
            f(*e)
        }
    }
}

///Generic trait for tallied ballot data
///
///Tally is a junction between ballot data parsers and election methods.
///It exists because some methods require only a beat table, so candidates^2 integers
///(`VoteMatrix`), while other the counts for all possible ballot contents (`VoteTree`).
///
///On the other side, you can built given tally by collecting from iterator of vote tokens, use
///debug constructors of either implementers, or simply use `from_blt_file` to read from a `*.blt`
///file.
pub trait Tally: Sized {
    ///Returns a copy of metadata
    fn get_meta(&self) -> &'_ Metadata;
    ///Return a number of seats
    fn get_seats(&self) -> u32 {
        self.get_meta().get_seats()
    }
    ///Returns  the number of candidates
    fn get_candidates(&self) -> u32;
    ///Get the name of a candidate from metadata
    fn name_candidate(&self, id: u32) -> Option<&String> {
        self.get_meta().get_name(id)
    }
    ///Map over IDs of withdrawn candidates
    fn map_withdrawn<F: FnMut(u32)>(&self, f: F) {
        self.get_meta().map_withdrawn(f)
    }
    //TODO:  fn from_blt_buffer(), from_blt_file uses from_blt_buffer
    fn from_blt_file(name: &str) -> Result<Self, VoteReadError>
    where
        Result<Self, VoteReadError>: FromIterator<VoteToken>,
    {
        use crate::io::BltReader;
        use std::fs::File;
        use std::io::BufReader;
        BltReader::new(&mut BufReader::new(File::open(name)?)).collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn no_file_errors() {
        use crate::VoteMatrix;
        let x: Result<VoteMatrix, _> = Tally::from_blt_file("non-existent");
        assert!(x.is_err());
    }
    #[test]
    fn file_read() {
        use crate::Irv;
        let x: Result<VoteTree, _> = Tally::from_blt_file("examples/a123.blt");
        assert!(Irv::new(&x.unwrap()).run().is_ok());
    }
}