Refactor calculation of totals, etc.
This commit is contained in:
parent
6ff111054c
commit
823f06a32b
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue