diff --git a/src/ballot.rs b/src/ballot.rs index e069aca..3522b49 100644 --- a/src/ballot.rs +++ b/src/ballot.rs @@ -1,11 +1,6 @@ use itertools::Itertools; -use crate::header::Header; -#[derive(Copy,Clone,Debug,PartialEq)] -enum BallotType { - Party, - Candidate, -} +use crate::{header::Header, stats::Stats}; #[derive(Debug)] @@ -14,44 +9,43 @@ pub struct Ballot { weight: f64, } -impl Ballot { - pub fn parse(row: quick_csv::Row, header: &Header) -> Result> { - let cols = row.columns()?.skip(6).collect_vec(); - let mut votes: Vec>; - let ty: BallotType; +const CANDIDATE_MIN: usize = 6; - let col_filter = |(index, place): (usize, &str)| match place.parse::() { - Ok(v) => Some((index, v - 1)), +impl Ballot { + pub fn parse(row: quick_csv::Row, header: &Header, stats: &mut Stats) -> Result> { + let mut cols = row.columns()?.skip(6); + + let place_filter = |(index, place): (usize, &str)| match place.parse::() { + Ok(place) => Some((place, index)), Err(_) => None, }; - // below the line votes - if cols.len() > header.parties.len() { - ty = BallotType::Candidate; - votes = vec![None; header.candidates.len()]; - for (col_id, place) in cols.iter().copied().skip(header.parties.len()).take(header.candidates.len()).enumerate().filter_map(col_filter) { - votes[place] = Some(col_id); - } + let mut votes_party = cols.by_ref().take(header.parties.len()).enumerate().filter_map(place_filter).collect_vec(); + let mut votes_candidate = cols.take(header.candidates.len()).enumerate().filter_map(place_filter).collect_vec(); + let votes; + + if votes_candidate.len() > 0 && votes_party.len() > 0 { + stats.both += 1; + } + + if votes_candidate.len() >= CANDIDATE_MIN.min(header.candidates.len()) { + votes_candidate.sort_by(|a, b| i32::cmp(&a.0, &b.0)); + votes = votes_candidate.into_iter().map(|(_,id)| id).collect_vec(); + stats.candidate += 1; + } + + else if votes_party.len() > 0 { + votes_party.sort_by(|a, b| i32::cmp(&a.0, &b.0)); + votes = votes_party.into_iter().map(|(_,id)| header.parties[id].member_ids.iter().copied()).flatten().collect_vec(); + stats.party += 1; } - // above the line votes else { - ty = BallotType::Party; - votes = vec![None; header.parties.len()]; - for (col_id, place) in cols.iter().copied().take(header.parties.len()).enumerate().filter_map(col_filter) { - votes[place] = Some(col_id); - } + return Err("ballot is informal".into()); } - let votes = match ty { - BallotType::Candidate => votes.iter().filter_map(|&v| v).collect(), - BallotType::Party => votes.iter().filter_map(|&v| v).map(|id| header.parties[id].member_ids.iter().copied()).flatten().collect(), - }; - - Ok(Ballot { - weight: 1.0, - votes, - }) + stats.total += 1; + Ok(Ballot { weight: 1.0, votes }) } pub fn get_weight(&self) -> f64 { self.weight diff --git a/src/counter.rs b/src/counter.rs index bd1b425..7c55396 100644 --- a/src/counter.rs +++ b/src/counter.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use itertools::Itertools; -use crate::{ballot::Ballot, header::Header, util::ScoreItem}; +use crate::{ballot::Ballot, header::Header, stats::Stats, util::ScoreItem}; #[derive(Debug)] pub enum Event { @@ -13,6 +13,7 @@ pub enum Event { pub struct Counter { pub header: Rc
, pub ballots: Vec, + pub stats: Stats, winners_left: usize, enabled: Vec, quota: f64, @@ -22,13 +23,14 @@ impl Counter { pub fn new(mut csv: quick_csv::Csv, winners: usize) -> Result> { let header = Rc::new(Header::parse(csv.next().ok_or("csv header missing")??, winners)?); let mut ballots = Vec::new(); + let mut stats = Stats::new(); if winners > header.candidates.len() { return Err("winners can't be smaller than the candidates list".into()); } for row in csv { - ballots.push(Ballot::parse(row?, &header)?); + ballots.push(Ballot::parse(row?, &header, &mut stats)?); } let enabled = vec![true; header.candidates.len()]; @@ -38,6 +40,7 @@ impl Counter { Ok(Counter { header, ballots, + stats, winners_left, enabled, quota, diff --git a/src/main.rs b/src/main.rs index 700db9a..f59b71b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ pub mod candidate; pub mod ballot; pub mod header; pub mod party; +pub mod stats; mod counter; fn main() { @@ -27,7 +28,11 @@ fn main() { eprintln!("Parties: {}", header.parties.len()); eprintln!("Candidates: {}", header.candidates.len()); eprintln!("Vacancies: {}", winner_count); - eprintln!("Ballots: {}", counter.ballots.len()); + eprintln!("Ballots:"); + eprintln!(" Total: {}", counter.stats.total); + eprintln!(" Above: {}, {}", counter.stats.party, Percent(counter.stats.party.into(), counter.stats.total.into())); + eprintln!(" Below: {}, {}", counter.stats.candidate, Percent(counter.stats.candidate.into(), counter.stats.total.into())); + eprintln!(" Both: {}, {}", counter.stats.both, Percent(counter.stats.both.into(), counter.stats.total.into())); eprintln!("Quota: {}, {}", counter.get_quota(), Percent(counter.get_quota(), total)); eprintln!(); diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 0000000..c28f470 --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,22 @@ +use std::fmt::Debug; + + +#[derive(Debug)] +pub struct Stats { + pub total: u32, + pub candidate: u32, + pub party: u32, + pub both: u32, +} + +impl Stats { + pub fn new() -> Self { + Self { + total: 0, + party: 0, + candidate: 0, + both: 0, + } + } +} + diff --git a/src/util/percent.rs b/src/util/percent.rs index 8a41ad7..5156bbd 100644 --- a/src/util/percent.rs +++ b/src/util/percent.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; pub struct Percent(pub f64, pub f64); @@ -9,6 +9,11 @@ impl Display for Percent { write!(f, "{:.3}%", self.0 / self.1 * 100.0) } } +impl Debug for Percent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Percent({}, {:.3}%)", self.0, self.0 / self.1 * 100.0) + } +} impl Display for PercentDiff { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let v = self.0 / self.1 * 100.0; @@ -18,4 +23,9 @@ impl Display for PercentDiff { }, v.abs()) } } +impl Debug for PercentDiff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Percent({}, {:.3}%)", self.0, self.0 / self.1 * 100.0) + } +}