diff --git a/Cargo.lock b/Cargo.lock index 4a77ceb..0a7180b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - [[package]] name = "ballot-counter" version = "0.1.0" dependencies = [ "itertools", - "num", "quick-csv", ] @@ -32,79 +25,6 @@ dependencies = [ "either", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "quick-csv" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 0d5070b..1b6162a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,4 @@ edition = "2024" [dependencies] itertools = "0.14.0" -num = "0.4.3" quick-csv = "0.1.6" diff --git a/src/counter.rs b/src/counter.rs index 0285da9..bd1b425 100644 --- a/src/counter.rs +++ b/src/counter.rs @@ -1,10 +1,11 @@ use std::rc::Rc; use itertools::Itertools; -use crate::{ballot::Ballot, header::Header, score_item::{ScoreItem, ScoreItems}}; + +use crate::{ballot::Ballot, header::Header, util::ScoreItem}; #[derive(Debug)] pub enum Event { - Lose(Vec), + Lose(ScoreItem), Win(Vec), } @@ -42,14 +43,17 @@ impl Counter { quota, }) } - fn count_primaries(&self) -> ScoreItems { + fn count_primaries(&self) -> Vec { let mut scores = vec![0.0; self.enabled.len()]; for ballot in self.ballots.iter() { if let Some(index) = ballot.get_primary_index(|id| self.enabled[id]) { scores[index] += ballot.get_weight(); } } - ScoreItems::new(scores, &self.enabled) + scores.into_iter().enumerate().filter_map(|(index, value)| match self.enabled[index] { + true => Some(ScoreItem::new(index, value)), + false => None, + }).collect_vec() } pub fn get_quota(&self) -> f64 { self.quota @@ -61,38 +65,24 @@ impl Iterator for Counter { fn next(&mut self) -> Option { let mut winners = Vec::::with_capacity(self.enabled.len()); - let mut losers = Vec::::with_capacity(self.enabled.len()); - let mut primaries = self.count_primaries().collect_vec(); - primaries.sort_by(|a, b| a.cmp(b)); - - let &lowest = primaries.first()?; - let &highest = primaries.last()?; - let mut lose_total = 0.0; + let primaries = self.count_primaries(); + let mut lowest = *primaries.first()?; for &score in primaries.iter() { + lowest = lowest.min(score); if score.value >= self.quota { winners.push(score); } - if lose_total + score.value < highest.value { - lose_total += score.value; - losers.push(score); - } } if primaries.len() == 1 && self.winners_left == 1 { - self.enabled[highest.index] = false; - return Some(Event::Win(vec![highest])); - } - - if losers.len() == 0 { - losers.push(lowest); + self.enabled[lowest.index] = false; + return Some(Event::Win(vec![lowest])); } if winners.len() == 0 { - for score in losers.iter() { - self.enabled[score.index] = false; - } - return Some(Event::Lose(losers)); + self.enabled[lowest.index] = false; + return Some(Event::Lose(lowest)); } winners.sort_by(|a,b| b.cmp(a)); diff --git a/src/main.rs b/src/main.rs index e9990a3..700db9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ pub mod candidate; pub mod ballot; pub mod header; pub mod party; -pub mod score_item; mod counter; fn main() { @@ -36,18 +35,15 @@ fn main() { for (index, event) in (1..).zip(counter) { match event { - counter::Event::Win(v) => { + counter::Event::Win(scores) => { eprintln!(" {index}: Win:"); - for winner in v.iter() { + for winner in scores.iter() { eprintln!(" - {}, {}, {}", header.get_candidate_name(winner.index), winner.value, Percent(winner.value, total)); } - winners.extend_from_slice(&v); + winners.extend(scores); } - counter::Event::Lose(v) => { - eprintln!(" {index}: Lose:"); - for loser in v.iter() { - eprintln!(" - {}, {}, {}", header.get_candidate_name(loser.index), loser.value, Percent(loser.value, total)); - } + counter::Event::Lose(loser) => { + eprintln!(" {index}: Lose: {}, {}, {}", header.get_candidate_name(loser.index), loser.value, Percent(loser.value, total)); } } } diff --git a/src/score_item.rs b/src/score_item.rs deleted file mode 100644 index 4a5388b..0000000 --- a/src/score_item.rs +++ /dev/null @@ -1,61 +0,0 @@ - -#[derive(Clone,Copy,Debug)] -pub struct ScoreItem { - pub index: usize, - pub value: f64, -} - -pub struct ScoreItems<'a> { - enabled: &'a [bool], - data: Vec, - index: usize, -} - -impl ScoreItem { - pub fn new(index: usize, value: f64) -> Self { - Self { index, value } - } - pub fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.value.total_cmp(&other.value) - } - pub fn max(self, other: Self) -> Self { - if self.value > other.value { - self - } else { - other - } - } - pub fn min(self, other: Self) -> Self { - if self.value > other.value { - other - } else { - self - } - } -} - -impl<'a> ScoreItems<'a> { - pub fn new(data: Vec, enabled: &'a [bool]) -> Self { - Self { data, enabled, index: 0 } - } -} - -impl<'a> Iterator for ScoreItems<'a> { - type Item = ScoreItem; - fn next(&mut self) -> Option { - while self.index < self.data.len() { - if self.enabled[self.index] { - let v = ScoreItem::new(self.index, self.data[self.index]); - self.index += 1; - return Some(v); - } - self.index += 1; - } - None - } - fn size_hint(&self) -> (usize, Option) { - let left = self.data.len() - self.index; - (0, Some(left)) - } -} - diff --git a/src/util.rs b/src/util.rs index 3349e60..5858cef 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,10 @@ pub mod escape; pub mod percent; +pub mod score_item; pub mod csv; +pub use score_item::ScoreItem; pub use escape::{EscapeWriter, EscapeWriterOpts}; pub use percent::Percent; pub use csv::CsvWriter; diff --git a/src/util/csv.rs b/src/util/csv.rs index 1c21d06..c6ccda5 100644 --- a/src/util/csv.rs +++ b/src/util/csv.rs @@ -39,7 +39,7 @@ impl<'a,T> RowWriter<'a,T> where T: std::io::Write { write!(self.csv.out, "\"")?; let mut writer = EscapeWriter::new(&mut self.csv.out, EscapeWriterOpts { escape: b'\\', - chars: b"\"", + chars: b"\"\t\r\n", }); write!(writer, "{value}")?; write!(self.csv.out, "\"")?; diff --git a/src/util/score_item.rs b/src/util/score_item.rs new file mode 100644 index 0000000..72fc858 --- /dev/null +++ b/src/util/score_item.rs @@ -0,0 +1,30 @@ + +#[derive(Clone,Copy,Debug)] +pub struct ScoreItem { + pub index: usize, + pub value: f64, +} + +impl ScoreItem { + pub fn new(index: usize, value: f64) -> Self { + Self { index, value } + } + pub fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.value.total_cmp(&other.value) + } + pub fn max(self, other: Self) -> Self { + if self.value > other.value { + self + } else { + other + } + } + pub fn min(self, other: Self) -> Self { + if self.value > other.value { + other + } else { + self + } + } +} +