removed an optimisation that makes things less accurate

This commit is contained in:
Jay Robson 2025-05-07 12:49:53 +10:00
parent 96f1775004
commit 41c4833a2b
8 changed files with 53 additions and 177 deletions

80
Cargo.lock generated
View File

@ -2,18 +2,11 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "ballot-counter" name = "ballot-counter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"itertools", "itertools",
"num",
"quick-csv", "quick-csv",
] ]
@ -32,79 +25,6 @@ dependencies = [
"either", "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]] [[package]]
name = "quick-csv" name = "quick-csv"
version = "0.1.6" version = "0.1.6"

View File

@ -5,5 +5,4 @@ edition = "2024"
[dependencies] [dependencies]
itertools = "0.14.0" itertools = "0.14.0"
num = "0.4.3"
quick-csv = "0.1.6" quick-csv = "0.1.6"

View File

@ -1,10 +1,11 @@
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools; use itertools::Itertools;
use crate::{ballot::Ballot, header::Header, score_item::{ScoreItem, ScoreItems}};
use crate::{ballot::Ballot, header::Header, util::ScoreItem};
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
Lose(Vec<ScoreItem>), Lose(ScoreItem),
Win(Vec<ScoreItem>), Win(Vec<ScoreItem>),
} }
@ -42,14 +43,17 @@ impl Counter {
quota, quota,
}) })
} }
fn count_primaries(&self) -> ScoreItems { fn count_primaries(&self) -> Vec<ScoreItem> {
let mut scores = vec![0.0; self.enabled.len()]; let mut scores = vec![0.0; self.enabled.len()];
for ballot in self.ballots.iter() { for ballot in self.ballots.iter() {
if let Some(index) = ballot.get_primary_index(|id| self.enabled[id]) { if let Some(index) = ballot.get_primary_index(|id| self.enabled[id]) {
scores[index] += ballot.get_weight(); 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 { pub fn get_quota(&self) -> f64 {
self.quota self.quota
@ -61,38 +65,24 @@ impl Iterator for Counter {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut winners = Vec::<ScoreItem>::with_capacity(self.enabled.len()); let mut winners = Vec::<ScoreItem>::with_capacity(self.enabled.len());
let mut losers = Vec::<ScoreItem>::with_capacity(self.enabled.len()); let primaries = self.count_primaries();
let mut primaries = self.count_primaries().collect_vec(); let mut lowest = *primaries.first()?;
primaries.sort_by(|a, b| a.cmp(b));
let &lowest = primaries.first()?;
let &highest = primaries.last()?;
let mut lose_total = 0.0;
for &score in primaries.iter() { for &score in primaries.iter() {
lowest = lowest.min(score);
if score.value >= self.quota { if score.value >= self.quota {
winners.push(score); 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 { if primaries.len() == 1 && self.winners_left == 1 {
self.enabled[highest.index] = false; self.enabled[lowest.index] = false;
return Some(Event::Win(vec![highest])); return Some(Event::Win(vec![lowest]));
}
if losers.len() == 0 {
losers.push(lowest);
} }
if winners.len() == 0 { if winners.len() == 0 {
for score in losers.iter() { self.enabled[lowest.index] = false;
self.enabled[score.index] = false; return Some(Event::Lose(lowest));
}
return Some(Event::Lose(losers));
} }
winners.sort_by(|a,b| b.cmp(a)); winners.sort_by(|a,b| b.cmp(a));

View File

@ -7,7 +7,6 @@ pub mod candidate;
pub mod ballot; pub mod ballot;
pub mod header; pub mod header;
pub mod party; pub mod party;
pub mod score_item;
mod counter; mod counter;
fn main() { fn main() {
@ -36,18 +35,15 @@ fn main() {
for (index, event) in (1..).zip(counter) { for (index, event) in (1..).zip(counter) {
match event { match event {
counter::Event::Win(v) => { counter::Event::Win(scores) => {
eprintln!(" {index}: Win:"); 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)); 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));
} }
} }
} }

View File

@ -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<f64>,
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<f64>, enabled: &'a [bool]) -> Self {
Self { data, enabled, index: 0 }
}
}
impl<'a> Iterator for ScoreItems<'a> {
type Item = ScoreItem;
fn next(&mut self) -> Option<Self::Item> {
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<usize>) {
let left = self.data.len() - self.index;
(0, Some(left))
}
}

View File

@ -1,8 +1,10 @@
pub mod escape; pub mod escape;
pub mod percent; pub mod percent;
pub mod score_item;
pub mod csv; pub mod csv;
pub use score_item::ScoreItem;
pub use escape::{EscapeWriter, EscapeWriterOpts}; pub use escape::{EscapeWriter, EscapeWriterOpts};
pub use percent::Percent; pub use percent::Percent;
pub use csv::CsvWriter; pub use csv::CsvWriter;

View File

@ -39,7 +39,7 @@ impl<'a,T> RowWriter<'a,T> where T: std::io::Write {
write!(self.csv.out, "\"")?; write!(self.csv.out, "\"")?;
let mut writer = EscapeWriter::new(&mut self.csv.out, EscapeWriterOpts { let mut writer = EscapeWriter::new(&mut self.csv.out, EscapeWriterOpts {
escape: b'\\', escape: b'\\',
chars: b"\"", chars: b"\"\t\r\n",
}); });
write!(writer, "{value}")?; write!(writer, "{value}")?;
write!(self.csv.out, "\"")?; write!(self.csv.out, "\"")?;

30
src/util/score_item.rs Normal file
View File

@ -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
}
}
}