wybr 0.0.6

Collection of preferential voting methods
Documentation
use crate::tally::Metadata;
use std::collections::{HashMap, HashSet};

#[derive(Clone)]
pub struct VoteMatrix {
    n: u32,
    data: HashMap<(u32, u32), u64>,
    meta: Metadata,
}

pub struct VoteMatrixPairIterator<'a> {
    wrapped: std::collections::hash_map::Iter<'a, (u32, u32), u64>,
    base: &'a VoteMatrix,
}
impl<'a> Iterator for VoteMatrixPairIterator<'a> {
    type Item = ((u32, u32), (u64, u64));
    fn next(&mut self) -> Option<Self::Item> {
        if let Some((&(i, j), &v)) = self.wrapped.next() {
            let alt = self.base[(j, i)];
            Some(((i, j), (v, alt)))
        } else {
            None
        }
    }
}

impl VoteMatrix {
    //TODO: Make an actual error here
    //Panics on a wrong size of x
    pub fn with_vector_bycol(x: &[u64]) -> VoteMatrix {
        if x.is_empty() {
            return VoteMatrix {
                n: 0,
                data: HashMap::new(),
                meta: Metadata::empty(),
            };
        }
        let s: f64 = x.len() as f64;
        let ln = (1. + (1. + 4. * s).sqrt()) / 2.;
        let mut data: HashMap<(u32, u32), u64> = HashMap::new();

        if ln.fract() != 0. {
            panic!("Cannot build VoteMatrix from a vector of such size");
        }
        let n: u32 = ln as u32;
        let mut ii = x.iter();
        for j in 0..n {
            for i in 0..n {
                if i != j {
                    let ne = *ii.next().unwrap();
                    if ne > 0 {
                        data.insert((i, j), ne);
                    }
                }
            }
        }
        VoteMatrix {
            n,
            data,
            meta: Metadata::empty(),
        }
    }
    pub fn pairs(&self) -> VoteMatrixPairIterator {
        VoteMatrixPairIterator {
            wrapped: self.data.iter(),
            base: self,
        }
    }
}

use crate::tally::Tally;
impl Tally for VoteMatrix {
    fn get_meta(&self) -> &'_ Metadata {
        &self.meta
    }
    fn get_candidates(&self) -> u32 {
        self.n
    }
}

use std::fmt;
impl fmt::Debug for VoteMatrix {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "/ {0}x{0} VoteMatrix:\n|", self.n)?;
        for i in 0..self.n {
            for j in 0..self.n {
                if i != j {
                    write!(f, "{:^5}", &self.data.get(&(i, j)).unwrap_or(&0))?;
                } else {
                    write!(f, "  -  ")?;
                }
            }
            write!(f, "\n|")?
        }
        writeln!(f, ".\n")
    }
}

//Index only (not IndexMut) on purpose; we don't like messing inside
use std::ops::Index;
impl Index<(u32, u32)> for VoteMatrix {
    type Output = u64;
    fn index(&self, ij: (u32, u32)) -> &u64 {
        self.data.get(&ij).unwrap_or(&0)
    }
}

use std::iter::FromIterator;
impl FromIterator<(u32, u32, u64)> for VoteMatrix {
    fn from_iter<I: IntoIterator<Item = (u32, u32, u64)>>(i: I) -> Self {
        let data: HashMap<(u32, u32), u64> = i.into_iter().map(|(i, j, k)| ((i, j), k)).collect();
        let n = if let Some(cmax) = data.keys().map(|&(i, j)| std::cmp::max(i, j)).max() {
            cmax + 1
        } else {
            0
        };
        VoteMatrix {
            n,
            data,
            meta: Metadata::empty(),
        }
    }
}

use self::VoteToken::*;
use crate::tally::{VoteReadError, VoteToken};
impl FromIterator<VoteToken> for Result<VoteMatrix, VoteReadError> {
    fn from_iter<I: IntoIterator<Item = VoteToken>>(i: I) -> Self {
        //Global state
        let mut data: HashMap<(u32, u32), u64> = HashMap::new();
        let mut withdrawn: HashSet<u32> = HashSet::new();
        let mut candidates: Option<u32> = None;
        let mut candidate_names: HashMap<u32, String> = HashMap::new();
        let mut title: Option<String> = None;
        let mut seats: Option<u32> = None;

        //Per-ballot state
        let mut ballot: HashMap<u32, i32> = HashMap::new();
        let mut ballot_repeat: Option<u64> = None;

        for token in i {
            match token {
                ReadFailure(x) => return Err(x),
                DeclareCandidates(x) => candidates = candidates.or(Some(x)),
                DeclareSeats(x) => seats = seats.or(Some(x)),
                WithdrawCandidate(x) => {
                    withdrawn.insert(x);
                }
                BallotRepeat(x) => ballot_repeat = Some(x),
                Vote(pref, cand) => {
                    ballot.insert(cand, pref);
                }
                EndBallot => {
                    let cn = candidates.unwrap();
                    //The following can be removed for unmentioned-indifferent case
                    for cand in 0..cn {
                        ballot.entry(cand).or_insert(i32::MAX);
                    }
                    for (&runner, &r_pref) in &ballot {
                        for (&opponent, &o_pref) in &ballot {
                            if r_pref < o_pref {
                                *data.entry((runner, opponent)).or_insert(0) +=
                                    ballot_repeat.unwrap(); //TODO: As above
                            }
                        }
                    }
                    //Clear the ballot cache
                    ballot_repeat = None;
                    ballot.clear();
                }
                ElectionTitle(x) => title = title.or(Some(x)),
                CandidateName(cand, name) => {
                    candidate_names.insert(cand, name);
                }
                _ => (),
            }
        }
        if let Some(n) = candidates {
            Ok(VoteMatrix {
                n,
                data,
                meta: Metadata::new(seats, withdrawn, candidate_names, title),
            })
        } else {
            Err(VoteReadError::InvalidMeta)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn from_blt_and_idx() {
        use crate::io::BltReader;
        use std::collections::HashSet;
        use std::io::Cursor;
        const EASY_BLT: &'static [u8] =
            b" 3  1\n -3 \n 7  1 =2  =3 0\n \n  17 2  3 1  0\n 0 17 8 0\n27 3 2  1 0\n 0 \n \"A\"\n  \n\"B\" \n\"C\"\n \"Title\" \n";
        let mut cursor = Cursor::new(EASY_BLT);
        let blt_reader = BltReader::new(&mut cursor);
        let vc = blt_reader
            .collect::<Result<VoteMatrix, VoteReadError>>()
            .unwrap();
        assert_eq!(vc[(1, 0)], 44);
        assert_eq!(vc[(2, 0)], 44);
        assert_eq!(vc[(0, 1)], 0);
        assert_eq!(vc[(2, 1)], 27);
        assert_eq!(vc[(0, 2)], 0);
        assert_eq!(vc[(1, 2)], 17);
        //As above, but without 0s
        let ps = vc.pairs().collect::<HashSet<_>>();
        assert!(ps.contains(&((2, 0), (44, 0))));
        assert!(ps.contains(&((2, 1), (27, 17))));
        assert!(ps.contains(&((1, 0), (44, 0))));
        assert!(ps.contains(&((1, 2), (17, 27))));
        assert_eq!(ps.len(), 4);
    }
}