From 823f06a32b9fe5292d57ee9ac9ddce1729ac0cb4 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Sun, 21 Aug 2022 07:20:21 +1000 Subject: [PATCH] Refactor calculation of totals, etc. --- src/election.rs | 52 ++++++++++++++++++++++++++++++++ src/stv/gregory/mod.rs | 5 ++-- src/stv/meek.rs | 11 +++---- src/stv/mod.rs | 67 +++++++----------------------------------- 4 files changed, 68 insertions(+), 67 deletions(-) diff --git a/src/election.rs b/src/election.rs index ddc3603..0d39ec0 100644 --- a/src/election.rs +++ b/src/election.rs @@ -334,6 +334,58 @@ impl<'a, N: Number> CountState<'a, N> { return result; } + + // ------------------- + // HELPER CALCULATIONS + + /// Get the total vote, viz. the sum votes of all candidates + pub fn total_vote(&self) -> N { + return self.total_votes_of(self.candidates.values()); + } + + /// Get the total votes of the given candidates + pub fn total_votes_of>>(&self, count_cards: I) -> N { + return count_cards.fold(N::new(), |mut acc, cc| { acc += &cc.votes; acc }); + } + + /// Get the total active vote, viz. the votes of all continuing candidates plus all votes awaiting transfer + pub fn active_vote(&self) -> N { + return self.candidates.values().fold(N::new(), |mut acc, cc| { + match cc.state { + CandidateState::Elected => { + if !cc.finalised && &cc.votes > self.quota.as_ref().unwrap() { + acc += &cc.votes; + acc -= self.quota.as_ref().unwrap(); + } + } + _ => { + acc += &cc.votes; + } + } + acc + }); + } + + /// Get the total surplus + /// + /// A candidate has a surplus if the candidate's votes are not finalised, and the votes exceed the quota. + /// The votes of all other candidates are ignored. + pub fn total_surplus(&self) -> N { + return self.total_surplus_of(self.candidates.values()); + } + + /// Get the total surpluses of the given candidates + /// + /// See [total_surplus]. + pub fn total_surplus_of>>(&self, count_cards: I) -> N { + return count_cards.fold(N::new(), |mut acc, cc| { + if !cc.finalised && &cc.votes > self.quota.as_ref().unwrap() { + acc += &cc.votes; + acc -= self.quota.as_ref().unwrap(); + } + acc + }); + } } /// The kind, title, etc. of the stage being counted diff --git a/src/stv/gregory/mod.rs b/src/stv/gregory/mod.rs index 8fc2f98..dd724ca 100644 --- a/src/stv/gregory/mod.rs +++ b/src/stv/gregory/mod.rs @@ -82,7 +82,7 @@ where // Calculate loss by fraction - if minivoters used if let Some(orig_total) = &state.election.total_votes { - let mut total_votes = state.candidates.values().fold(N::new(), |mut acc, cc| { acc += &cc.votes; acc }); + let mut total_votes = state.total_vote(); total_votes += &state.exhausted.votes; let lbf = orig_total - &total_votes; @@ -114,8 +114,7 @@ where .collect(); if !has_surplus.is_empty() { - let total_surpluses = has_surplus.iter() - .fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc }); + let total_surpluses = state.total_surplus(); // Determine if surplues can be deferred if opts.defer_surpluses { diff --git a/src/stv/meek.rs b/src/stv/meek.rs index 318187a..96662fc 100644 --- a/src/stv/meek.rs +++ b/src/stv/meek.rs @@ -134,7 +134,7 @@ where // Calculate loss by fraction - if minivoters used if let Some(orig_total) = &state.election.total_votes { - let mut total_votes = state.candidates.values().fold(N::new(), |mut acc, cc| { acc += &cc.votes; acc }); + let mut total_votes = state.total_vote(); total_votes += &state.exhausted.votes; let lbf = orig_total - &total_votes; @@ -244,8 +244,7 @@ where } else { // Distribute if the total surplus exceeds the tolerance let quota_tolerance = N::parse(&opts.meek_surplus_tolerance); - let total_surpluses = has_surplus.iter() - .fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= state.quota.as_ref().unwrap(); acc }); + let total_surpluses = state.total_surplus(); return total_surpluses > quota_tolerance; } } @@ -269,8 +268,7 @@ where if should_distribute { // Determine if surplues can be deferred if opts.defer_surpluses { - let total_surpluses = has_surplus.iter() - .fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc }); + let total_surpluses = state.total_surplus(); if super::can_defer_surpluses(state, opts, &total_surpluses) { state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals)); return Ok(false); @@ -317,8 +315,7 @@ where // Determine if surplues can be deferred if should_distribute && opts.defer_surpluses { - let total_surpluses = has_surplus.iter() - .fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc }); + let total_surpluses = state.total_surplus(); if super::can_defer_surpluses(state, opts, &total_surpluses) { surpluses_deferred = Some(total_surpluses); break; diff --git a/src/stv/mod.rs b/src/stv/mod.rs index e2bc39e..0229382 100644 --- a/src/stv/mod.rs +++ b/src/stv/mod.rs @@ -904,20 +904,7 @@ fn update_vre_ers(state: &mut CountState, opts: &STVOptions) { let mut log = String::new(); // Calculate active vote - let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { - match cc.state { - CandidateState::Elected => { - if !cc.finalised && &cc.votes > state.quota.as_ref().unwrap() { - acc += &cc.votes; - acc -= state.quota.as_ref().unwrap(); - } - } - _ => { - acc += &cc.votes; - } - } - acc - }); + let active_vote = state.active_vote(); log.push_str(format!("Active vote is {:.dps$}, so the vote required for election is ", active_vote, dps=opts.pp_decimals).as_str()); let vote_req = active_vote / N::from(state.election.seats - state.num_elected + 1); @@ -952,20 +939,7 @@ fn update_vre_bulk(state: &mut CountState, _opts: &STVOptions) { //let mut log = String::new(); // Calculate active vote - let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { - match cc.state { - CandidateState::Elected => { - if !cc.finalised && &cc.votes > state.quota.as_ref().unwrap() { - acc += &cc.votes; - acc -= state.quota.as_ref().unwrap(); - } - } - _ => { - acc += &cc.votes; - } - } - acc - }); + let active_vote = state.active_vote(); //log.push_str(format!("Active vote is {:.dps$}, so the vote required for election is ", active_vote, dps=opts.pp_decimals).as_str()); let vote_req = active_vote / N::from(state.election.seats - state.num_elected + 1); @@ -995,7 +969,7 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { let mut log = String::new(); // Calculate the total vote - let total_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { acc += &cc.votes; acc }); + let total_vote = state.total_vote(); log.push_str(format!("{:.dps$} usable votes, so the quota is ", total_vote, dps=opts.pp_decimals).as_str()); let quota = total_to_quota(total_vote, state.election.seats, opts); @@ -1010,20 +984,7 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { let mut log = String::new(); // Calculate the active vote - let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { - match cc.state { - CandidateState::Elected => { - if !cc.finalised && &cc.votes > state.quota.as_ref().unwrap() { - acc += &cc.votes; - acc -= state.quota.as_ref().unwrap(); - } - } - _ => { - acc += &cc.votes; - } - } - acc - }); + let active_vote = state.active_vote(); log.push_str(format!("Active vote is {:.dps$}, so the quota is is ", active_vote, dps=opts.pp_decimals).as_str()); // TODO: Calculate according to --quota ? @@ -1044,7 +1005,7 @@ fn calculate_quota(state: &mut CountState, opts: &STVOptions) { let mut log = String::new(); // Calculate the total vote - let total_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { acc += &cc.votes; acc }); + let total_vote = state.total_vote(); log.push_str(format!("{:.dps$} usable votes, so the quota is reduced to ", total_vote, dps=opts.pp_decimals).as_str()); let quota = total_to_quota(total_vote, state.election.seats, opts); @@ -1119,14 +1080,9 @@ fn elect_sure_winners<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOp let mut total_trailing = N::new(); // For leading candidates, count only untransferred surpluses - total_trailing += hopefuls.iter().take(num_vacancies).fold(N::new(), |mut acc, (_, cc)| { - if &cc.votes > state.quota.as_ref().unwrap() { - acc += &cc.votes; acc -= state.quota.as_ref().unwrap(); - } - acc - }); + total_trailing += state.total_surplus_of(hopefuls.iter().take(num_vacancies).map(|(_, cc)| *cc)); // For trailing candidates, count all votes - total_trailing += hopefuls.iter().skip(num_vacancies).fold(N::new(), |mut acc, (_, cc)| { acc += &cc.votes; acc }); + total_trailing += state.total_votes_of(hopefuls.iter().skip(num_vacancies).map(|(_, cc)| *cc)); // Add finally any votes awaiting transfer total_trailing += state.candidates.values().fold(N::new(), |mut acc, cc| { match cc.state { @@ -1330,8 +1286,7 @@ where let to_exclude = hopefuls_to_bulk_exclude(state, opts); let num_to_exclude = to_exclude.len(); if num_to_exclude > 0 { - let total_excluded = to_exclude.into_iter() - .fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc }); + let total_excluded = state.total_votes_of(to_exclude.into_iter().map(|c| &state.candidates[c])); if total_surpluses >= &(&hopefuls[num_to_exclude].1.votes - &total_excluded) { return false; } @@ -1528,9 +1483,7 @@ fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &ST // NB: Unnecessary to handle ties, as ties will be rejected at "Do not exclude if this could change the order of exclusion" hopefuls.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes)); - let total_surpluses = state.candidates.iter() - .filter(|(_, cc)| !cc.finalised && &cc.votes > state.quota.as_ref().unwrap()) - .fold(N::new(), |mut acc, (_, cc)| { acc += &cc.votes; acc -= state.quota.as_ref().unwrap(); acc }); + let total_surpluses = state.total_surplus(); // Attempt to exclude as many candidates as possible for i in 0..hopefuls.len() { @@ -1542,7 +1495,7 @@ fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &ST } // Do not exclude if this could change the order of exclusion - let total_votes = try_exclude.iter().fold(N::new(), |mut acc, (_, cc)| { acc += &cc.votes; acc }); + let total_votes = state.total_votes_of(try_exclude.iter().map(|(_, cc)| *cc)); if i != 0 && total_votes + &total_surpluses >= hopefuls[hopefuls.len()-i].1.votes { continue; }