Refactor calculation of totals, etc.

This commit is contained in:
RunasSudo 2022-08-21 07:20:21 +10:00
parent 6ff111054c
commit 823f06a32b
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
4 changed files with 68 additions and 67 deletions

View File

@ -334,6 +334,58 @@ impl<'a, N: Number> CountState<'a, N> {
return result; 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<I: Iterator<Item=&'a CountCard<'a, N>>>(&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<I: Iterator<Item=&'a CountCard<'a, N>>>(&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 /// The kind, title, etc. of the stage being counted

View File

@ -82,7 +82,7 @@ where
// Calculate loss by fraction - if minivoters used // Calculate loss by fraction - if minivoters used
if let Some(orig_total) = &state.election.total_votes { 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; total_votes += &state.exhausted.votes;
let lbf = orig_total - &total_votes; let lbf = orig_total - &total_votes;
@ -114,8 +114,7 @@ where
.collect(); .collect();
if !has_surplus.is_empty() { if !has_surplus.is_empty() {
let total_surpluses = has_surplus.iter() let total_surpluses = state.total_surplus();
.fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc });
// Determine if surplues can be deferred // Determine if surplues can be deferred
if opts.defer_surpluses { if opts.defer_surpluses {

View File

@ -134,7 +134,7 @@ where
// Calculate loss by fraction - if minivoters used // Calculate loss by fraction - if minivoters used
if let Some(orig_total) = &state.election.total_votes { 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; total_votes += &state.exhausted.votes;
let lbf = orig_total - &total_votes; let lbf = orig_total - &total_votes;
@ -244,8 +244,7 @@ where
} else { } else {
// Distribute if the total surplus exceeds the tolerance // Distribute if the total surplus exceeds the tolerance
let quota_tolerance = N::parse(&opts.meek_surplus_tolerance); let quota_tolerance = N::parse(&opts.meek_surplus_tolerance);
let total_surpluses = has_surplus.iter() let total_surpluses = state.total_surplus();
.fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= state.quota.as_ref().unwrap(); acc });
return total_surpluses > quota_tolerance; return total_surpluses > quota_tolerance;
} }
} }
@ -269,8 +268,7 @@ where
if should_distribute { if should_distribute {
// Determine if surplues can be deferred // Determine if surplues can be deferred
if opts.defer_surpluses { if opts.defer_surpluses {
let total_surpluses = has_surplus.iter() let total_surpluses = state.total_surplus();
.fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc });
if super::can_defer_surpluses(state, opts, &total_surpluses) { 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)); state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals));
return Ok(false); return Ok(false);
@ -317,8 +315,7 @@ where
// Determine if surplues can be deferred // Determine if surplues can be deferred
if should_distribute && opts.defer_surpluses { if should_distribute && opts.defer_surpluses {
let total_surpluses = has_surplus.iter() let total_surpluses = state.total_surplus();
.fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc -= quota; acc });
if super::can_defer_surpluses(state, opts, &total_surpluses) { if super::can_defer_surpluses(state, opts, &total_surpluses) {
surpluses_deferred = Some(total_surpluses); surpluses_deferred = Some(total_surpluses);
break; break;

View File

@ -904,20 +904,7 @@ fn update_vre_ers<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
let mut log = String::new(); let mut log = String::new();
// Calculate active vote // Calculate active vote
let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { let active_vote = state.active_vote();
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
});
log.push_str(format!("Active vote is {:.dps$}, so the vote required for election is ", active_vote, dps=opts.pp_decimals).as_str()); 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); let vote_req = active_vote / N::from(state.election.seats - state.num_elected + 1);
@ -952,20 +939,7 @@ fn update_vre_bulk<N: Number>(state: &mut CountState<N>, _opts: &STVOptions) {
//let mut log = String::new(); //let mut log = String::new();
// Calculate active vote // Calculate active vote
let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { let active_vote = state.active_vote();
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
});
//log.push_str(format!("Active vote is {:.dps$}, so the vote required for election is ", active_vote, dps=opts.pp_decimals).as_str()); //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); let vote_req = active_vote / N::from(state.election.seats - state.num_elected + 1);
@ -995,7 +969,7 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
let mut log = String::new(); let mut log = String::new();
// Calculate the total vote // 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()); 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); let quota = total_to_quota(total_vote, state.election.seats, opts);
@ -1010,20 +984,7 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
let mut log = String::new(); let mut log = String::new();
// Calculate the active vote // Calculate the active vote
let active_vote = state.candidates.values().fold(N::new(), |mut acc, cc| { let active_vote = state.active_vote();
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
});
log.push_str(format!("Active vote is {:.dps$}, so the quota is is ", active_vote, dps=opts.pp_decimals).as_str()); 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 ? // TODO: Calculate according to --quota ?
@ -1044,7 +1005,7 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
let mut log = String::new(); let mut log = String::new();
// Calculate the total vote // 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()); 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); 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(); let mut total_trailing = N::new();
// For leading candidates, count only untransferred surpluses // For leading candidates, count only untransferred surpluses
total_trailing += hopefuls.iter().take(num_vacancies).fold(N::new(), |mut acc, (_, cc)| { total_trailing += state.total_surplus_of(hopefuls.iter().take(num_vacancies).map(|(_, cc)| *cc));
if &cc.votes > state.quota.as_ref().unwrap() {
acc += &cc.votes; acc -= state.quota.as_ref().unwrap();
}
acc
});
// For trailing candidates, count all votes // 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 // Add finally any votes awaiting transfer
total_trailing += state.candidates.values().fold(N::new(), |mut acc, cc| { total_trailing += state.candidates.values().fold(N::new(), |mut acc, cc| {
match cc.state { match cc.state {
@ -1330,8 +1286,7 @@ where
let to_exclude = hopefuls_to_bulk_exclude(state, opts); let to_exclude = hopefuls_to_bulk_exclude(state, opts);
let num_to_exclude = to_exclude.len(); let num_to_exclude = to_exclude.len();
if num_to_exclude > 0 { if num_to_exclude > 0 {
let total_excluded = to_exclude.into_iter() let total_excluded = state.total_votes_of(to_exclude.into_iter().map(|c| &state.candidates[c]));
.fold(N::new(), |mut acc, c| { acc += &state.candidates[c].votes; acc });
if total_surpluses >= &(&hopefuls[num_to_exclude].1.votes - &total_excluded) { if total_surpluses >= &(&hopefuls[num_to_exclude].1.votes - &total_excluded) {
return false; 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" // 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)); hopefuls.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
let total_surpluses = state.candidates.iter() let total_surpluses = state.total_surplus();
.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 });
// Attempt to exclude as many candidates as possible // Attempt to exclude as many candidates as possible
for i in 0..hopefuls.len() { 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 // 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 { if i != 0 && total_votes + &total_surpluses >= hopefuls[hopefuls.len()-i].1.votes {
continue; continue;
} }