Refactoring
This commit is contained in:
parent
9b73a4adf4
commit
0d9196a951
|
@ -227,7 +227,7 @@ impl ConstraintMatrix {
|
||||||
}).collect();
|
}).collect();
|
||||||
let cell = &mut self[&idx[..]];
|
let cell = &mut self[&idx[..]];
|
||||||
|
|
||||||
let count_card = candidates.get(candidate).unwrap();
|
let count_card = &candidates[candidate];
|
||||||
match count_card.state {
|
match count_card.state {
|
||||||
CandidateState::Hopeful | CandidateState::Guarded => {
|
CandidateState::Hopeful | CandidateState::Guarded => {
|
||||||
cell.cands += 1;
|
cell.cands += 1;
|
||||||
|
@ -468,7 +468,7 @@ impl ops::IndexMut<&[usize]> for ConstraintMatrix {
|
||||||
fn candidates_in_constraint_cell<'a, N: Number>(election: &'a Election<N>, candidates: &HashMap<&Candidate, CountCard<N>>, idx: &[usize]) -> Vec<&'a Candidate> {
|
fn candidates_in_constraint_cell<'a, N: Number>(election: &'a Election<N>, candidates: &HashMap<&Candidate, CountCard<N>>, idx: &[usize]) -> Vec<&'a Candidate> {
|
||||||
let mut result: Vec<&Candidate> = Vec::new();
|
let mut result: Vec<&Candidate> = Vec::new();
|
||||||
for (i, candidate) in election.candidates.iter().enumerate() {
|
for (i, candidate) in election.candidates.iter().enumerate() {
|
||||||
let cc = candidates.get(candidate).unwrap();
|
let cc = &candidates[candidate];
|
||||||
if cc.state != CandidateState::Hopeful {
|
if cc.state != CandidateState::Hopeful {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -257,18 +257,6 @@ impl<'a, N: Number> CountState<'a, N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of a stage of counting
|
|
||||||
pub struct StageResult<'a, N: Number> {
|
|
||||||
/// See [CountState::kind]
|
|
||||||
pub kind: Option<&'a str>,
|
|
||||||
/// See [CountState::title]
|
|
||||||
pub title: &'a String,
|
|
||||||
/// Detailed logs of this stage, rendered from [CountState::logger]
|
|
||||||
pub logs: Vec<String>,
|
|
||||||
/// Reference to the [CountState] of this stage
|
|
||||||
pub state: &'a CountState<'a, N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Current state of a [Candidate] during an election count
|
/// Current state of a [Candidate] during an election count
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CountCard<'a, N> {
|
pub struct CountCard<'a, N> {
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use opentally::constraints::Constraints;
|
use opentally::constraints::Constraints;
|
||||||
use opentally::election::{Candidate, CandidateState, CountCard, CountState, Election, StageResult};
|
use opentally::election::{Candidate, CandidateState, CountCard, CountState, Election};
|
||||||
use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
|
use opentally::numbers::{Fixed, GuardedFixed, NativeFloat64, Number, Rational};
|
||||||
use opentally::stv;
|
use opentally::stv;
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ where
|
||||||
// Distribute first preferences
|
// Distribute first preferences
|
||||||
stv::count_init(&mut state, &stv_opts).unwrap();
|
stv::count_init(&mut state, &stv_opts).unwrap();
|
||||||
let mut stage_num = 1;
|
let mut stage_num = 1;
|
||||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
print_stage(stage_num, &state, &cmd_opts);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let is_done = stv::count_one_stage(&mut state, &stv_opts);
|
let is_done = stv::count_one_stage(&mut state, &stv_opts);
|
||||||
|
@ -294,7 +294,7 @@ where
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
stage_num += 1;
|
stage_num += 1;
|
||||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
print_stage(stage_num, &state, &cmd_opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Count complete. The winning candidates are, in order of election:");
|
println!("Count complete. The winning candidates are, in order of election:");
|
||||||
|
@ -305,7 +305,7 @@ where
|
||||||
winners.push((candidate, count_card));
|
winners.push((candidate, count_card));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
winners.sort_unstable_by(|a, b| a.1.order_elected.partial_cmp(&b.1.order_elected).unwrap());
|
winners.sort_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected));
|
||||||
|
|
||||||
for (i, (winner, count_card)) in winners.into_iter().enumerate() {
|
for (i, (winner, count_card)) in winners.into_iter().enumerate() {
|
||||||
if let Some(kv) = &count_card.keep_value {
|
if let Some(kv) = &count_card.keep_value {
|
||||||
|
@ -350,15 +350,15 @@ fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a Cou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>, cmd_opts: &STV) {
|
fn print_stage<N: Number>(stage_num: usize, state: &CountState<N>, cmd_opts: &STV) {
|
||||||
// Print stage details
|
let logs = state.logger.render();
|
||||||
match result.kind {
|
|
||||||
None => { println!("{}. {}", stage_num, result.title); }
|
|
||||||
Some(kind) => { println!("{}. {} {}", stage_num, kind, result.title); }
|
|
||||||
};
|
|
||||||
println!("{}", result.logs.join(" "));
|
|
||||||
|
|
||||||
let state = result.state;
|
// Print stage details
|
||||||
|
match state.kind {
|
||||||
|
None => { println!("{}. {}", stage_num, state.title); }
|
||||||
|
Some(kind) => { println!("{}. {} {}", stage_num, kind, state.title); }
|
||||||
|
};
|
||||||
|
println!("{}", logs.join(" "));
|
||||||
|
|
||||||
// Print candidates
|
// Print candidates
|
||||||
if cmd_opts.sort_votes {
|
if cmd_opts.sort_votes {
|
||||||
|
@ -366,13 +366,13 @@ fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>, cmd_opts: &
|
||||||
let mut candidates: Vec<(&Candidate, &CountCard<N>)> = state.candidates.iter()
|
let mut candidates: Vec<(&Candidate, &CountCard<N>)> = state.candidates.iter()
|
||||||
.map(|(c, cc)| (*c, cc)).collect();
|
.map(|(c, cc)| (*c, cc)).collect();
|
||||||
// First sort by order of election (as a tie-breaker, if votes are equal)
|
// First sort by order of election (as a tie-breaker, if votes are equal)
|
||||||
candidates.sort_unstable_by(|a, b| b.1.order_elected.partial_cmp(&a.1.order_elected).unwrap());
|
candidates.sort_unstable_by(|a, b| b.1.order_elected.cmp(&a.1.order_elected));
|
||||||
// Then sort by votes
|
// Then sort by votes
|
||||||
candidates.sort_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
|
candidates.sort_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
||||||
print_candidates(candidates.into_iter().rev(), cmd_opts);
|
print_candidates(candidates.into_iter().rev(), cmd_opts);
|
||||||
} else {
|
} else {
|
||||||
let candidates = state.election.candidates.iter()
|
let candidates = state.election.candidates.iter()
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()));
|
.map(|c| (c, &state.candidates[c]));
|
||||||
print_candidates(candidates, cmd_opts);
|
print_candidates(candidates, cmd_opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,13 +392,3 @@ fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>, cmd_opts: &
|
||||||
|
|
||||||
println!("");
|
println!("");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_and_print_result<N: Number>(stage_num: usize, state: &CountState<N>, cmd_opts: &STV) {
|
|
||||||
let result = StageResult {
|
|
||||||
kind: state.kind,
|
|
||||||
title: &state.title,
|
|
||||||
logs: state.logger.render(),
|
|
||||||
state: &state,
|
|
||||||
};
|
|
||||||
print_stage(stage_num, &result, &cmd_opts);
|
|
||||||
}
|
|
||||||
|
|
|
@ -126,12 +126,6 @@ impl From<usize> for Fixed {
|
||||||
fn from(n: usize) -> Self { Self(IBig::from(n) * get_factor()) }
|
fn from(n: usize) -> Self { Self(IBig::from(n) * get_factor()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for Fixed {
|
|
||||||
fn from(n: f64) -> Self {
|
|
||||||
return Self(IBig::from((n * 10_f64.powi(get_dps() as i32)).round() as u32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Fix rounding
|
// TODO: Fix rounding
|
||||||
impl fmt::Display for Fixed {
|
impl fmt::Display for Fixed {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
|
|
@ -159,12 +159,6 @@ impl From<usize> for GuardedFixed {
|
||||||
fn from(n: usize) -> Self { Self(IBig::from(n) * get_factor()) }
|
fn from(n: usize) -> Self { Self(IBig::from(n) * get_factor()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for GuardedFixed {
|
|
||||||
fn from(n: f64) -> Self {
|
|
||||||
return Self(IBig::from((n * 10_f64.powi((get_dps() * 2) as i32)).round() as u32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for GuardedFixed {
|
impl fmt::Display for GuardedFixed {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let dps = match f.precision() {
|
let dps = match f.precision() {
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub trait Assign<Src=Self> {
|
||||||
/// Trait for OpenTally numeric representations
|
/// Trait for OpenTally numeric representations
|
||||||
//pub trait Number: NumRef + NumAssignRef + PartialOrd + Assign + Clone + fmt::Display where for<'a> &'a Self: RefNum<&'a Self> {
|
//pub trait Number: NumRef + NumAssignRef + PartialOrd + Assign + Clone + fmt::Display where for<'a> &'a Self: RefNum<&'a Self> {
|
||||||
pub trait Number:
|
pub trait Number:
|
||||||
NumRef + NumAssignRef + ops::Neg<Output=Self> + Ord + Assign + From<usize> + From<f64> + Clone + fmt::Debug + fmt::Display
|
NumRef + NumAssignRef + ops::Neg<Output=Self> + Ord + Assign + From<usize> + Clone + fmt::Debug + fmt::Display
|
||||||
where
|
where
|
||||||
for<'a> Self: Assign<&'a Self>
|
for<'a> Self: Assign<&'a Self>
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,10 +71,6 @@ impl From<usize> for NativeFloat64 {
|
||||||
fn from(n: usize) -> Self { Self(n as ImplType) }
|
fn from(n: usize) -> Self { Self(n as ImplType) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for NativeFloat64 {
|
|
||||||
fn from(n: f64) -> Self { Self(n as ImplType) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl One for NativeFloat64 {
|
impl One for NativeFloat64 {
|
||||||
fn one() -> Self { Self(1.0) }
|
fn one() -> Self { Self(1.0) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,14 +108,6 @@ impl From<usize> for Rational {
|
||||||
fn from(n: usize) -> Self { Self(RatioType::from_integer(RatioBase::from(n))) }
|
fn from(n: usize) -> Self { Self(RatioType::from_integer(RatioBase::from(n))) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for Rational {
|
|
||||||
fn from(_n: f64) -> Self {
|
|
||||||
// FIXME: This is very broken!
|
|
||||||
//return Self(RatioType::from_float(n).unwrap() * RatioType::from_integer(100000).round() / RatioType::from_integer(100000));
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Rational {
|
impl fmt::Display for Rational {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(precision) = f.precision() {
|
if let Some(precision) = f.precision() {
|
||||||
|
|
|
@ -107,13 +107,6 @@ impl From<usize> for Rational {
|
||||||
fn from(n: usize) -> Self { Self(rug::Rational::from(n)) }
|
fn from(n: usize) -> Self { Self(rug::Rational::from(n)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<f64> for Rational {
|
|
||||||
fn from(n: f64) -> Self {
|
|
||||||
// FIXME: This is very broken!
|
|
||||||
return Self((rug::Rational::from_f64(n).unwrap() * rug::Rational::from(100000)).round() / rug::Rational::from(100000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Rational {
|
impl fmt::Display for Rational {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(precision) = f.precision() {
|
if let Some(precision) = f.precision() {
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
use super::{ExclusionMethod, NextPreferencesEntry, NextPreferencesResult, STVError, STVOptions, SumSurplusTransfersMode, SurplusMethod, SurplusOrder};
|
use super::{ExclusionMethod, NextPreferencesEntry, NextPreferencesResult, STVError, STVOptions, SumSurplusTransfersMode, SurplusMethod, SurplusOrder};
|
||||||
|
|
||||||
use crate::constraints;
|
use crate::constraints;
|
||||||
use crate::election::{Candidate, CandidateState, CountCard, CountState, Parcel, Vote};
|
use crate::election::{Candidate, CandidateState, CountState, Parcel, Vote};
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
|
use crate::ties;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
@ -62,15 +63,16 @@ where
|
||||||
for<'r> &'r N: ops::Neg<Output=N>
|
for<'r> &'r N: ops::Neg<Output=N>
|
||||||
{
|
{
|
||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
let mut has_surplus: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
let has_surplus: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.filter(|c| {
|
||||||
//.filter(|(_, cc)| &cc.votes > quota)
|
let cc = &state.candidates[c];
|
||||||
.filter(|(_, cc)| &cc.votes > quota && cc.parcels.iter().any(|p| !p.is_empty()))
|
&cc.votes > quota && cc.parcels.iter().any(|p| !p.is_empty())
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !has_surplus.is_empty() {
|
if !has_surplus.is_empty() {
|
||||||
let total_surpluses = has_surplus.iter()
|
let total_surpluses = has_surplus.iter()
|
||||||
.fold(N::new(), |acc, (_, cc)| acc + &cc.votes - quota);
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
|
||||||
|
|
||||||
// Determine if surplues can be deferred
|
// Determine if surplues can be deferred
|
||||||
if opts.defer_surpluses {
|
if opts.defer_surpluses {
|
||||||
|
@ -80,27 +82,20 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match opts.surplus_order {
|
// Distribute top candidate's surplus
|
||||||
|
let max_cands = match opts.surplus_order {
|
||||||
SurplusOrder::BySize => {
|
SurplusOrder::BySize => {
|
||||||
// Compare b with a to sort high-low
|
ties::multiple_max_by(&has_surplus, |c| &state.candidates[c].votes)
|
||||||
has_surplus.sort_by(|a, b| b.1.votes.cmp(&a.1.votes));
|
|
||||||
}
|
}
|
||||||
SurplusOrder::ByOrder => {
|
SurplusOrder::ByOrder => {
|
||||||
has_surplus.sort_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected));
|
ties::multiple_min_by(&has_surplus, |c| state.candidates[c].order_elected)
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
let elected_candidate = if max_cands.len() > 1 {
|
||||||
// Distribute top candidate's surplus
|
super::choose_highest(state, opts, max_cands)?
|
||||||
let elected_candidate;
|
|
||||||
|
|
||||||
// Handle ties
|
|
||||||
if has_surplus.len() > 1 && has_surplus[0].1.votes == has_surplus[1].1.votes {
|
|
||||||
let max_votes = &has_surplus[0].1.votes;
|
|
||||||
let has_surplus = has_surplus.into_iter().filter_map(|(c, cc)| if &cc.votes == max_votes { Some(c) } else { None }).collect();
|
|
||||||
elected_candidate = super::choose_highest(state, opts, has_surplus)?;
|
|
||||||
} else {
|
} else {
|
||||||
elected_candidate = has_surplus[0].0;
|
max_cands[0]
|
||||||
}
|
};
|
||||||
|
|
||||||
distribute_surplus(state, &opts, elected_candidate);
|
distribute_surplus(state, &opts, elected_candidate);
|
||||||
|
|
||||||
|
@ -230,14 +225,14 @@ where
|
||||||
{
|
{
|
||||||
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
|
state.logger.log_literal(format!("Surplus of {} distributed.", elected_candidate.name));
|
||||||
|
|
||||||
let count_card = state.candidates.get(elected_candidate).unwrap();
|
let count_card = &state.candidates[elected_candidate];
|
||||||
let surplus = &count_card.votes - state.quota.as_ref().unwrap();
|
let surplus = &count_card.votes - state.quota.as_ref().unwrap();
|
||||||
|
|
||||||
let votes;
|
let votes;
|
||||||
match opts.surplus {
|
match opts.surplus {
|
||||||
SurplusMethod::WIG | SurplusMethod::UIG => {
|
SurplusMethod::WIG | SurplusMethod::UIG => {
|
||||||
// Inclusive Gregory
|
// Inclusive Gregory
|
||||||
votes = state.candidates.get(elected_candidate).unwrap().parcels.concat();
|
votes = count_card.parcels.concat();
|
||||||
}
|
}
|
||||||
SurplusMethod::EG => {
|
SurplusMethod::EG => {
|
||||||
// Exclusive Gregory
|
// Exclusive Gregory
|
||||||
|
@ -274,9 +269,17 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.transferable_only {
|
if opts.transferable_only {
|
||||||
state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", &result.total_ballots - &result.exhausted.num_ballots, transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
if &result.total_ballots - &result.exhausted.num_ballots == N::one() {
|
||||||
|
state.logger.log_literal(format!("Transferring 1 transferable ballot, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} transferable ballots, totalling {:.dps$} transferable votes, with surplus fraction {:.dps2$}.", &result.total_ballots - &result.exhausted.num_ballots, transferable_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", result.total_ballots, result.total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
if result.total_ballots == N::one() {
|
||||||
|
state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", result.total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, with surplus fraction {:.dps2$}.", result.total_ballots, result.total_votes, surplus_fraction.as_ref().unwrap(), dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -389,7 +392,7 @@ where
|
||||||
ExclusionMethod::ByValue => {
|
ExclusionMethod::ByValue => {
|
||||||
// Exclude by value
|
// Exclude by value
|
||||||
let max_value = excluded_candidates.iter()
|
let max_value = excluded_candidates.iter()
|
||||||
.map(|c| state.candidates.get(c).unwrap().parcels.iter()
|
.map(|c| state.candidates[c].parcels.iter()
|
||||||
.map(|p| p.iter().map(|v| &v.value / &v.ballot.orig_value).max().unwrap())
|
.map(|p| p.iter().map(|v| &v.value / &v.ballot.orig_value).max().unwrap())
|
||||||
.max().unwrap())
|
.max().unwrap())
|
||||||
.max().unwrap();
|
.max().unwrap();
|
||||||
|
@ -451,9 +454,17 @@ where
|
||||||
let result = super::next_preferences(state, votes);
|
let result = super::next_preferences(state, votes);
|
||||||
|
|
||||||
if let ExclusionMethod::SingleStage = opts.exclusion {
|
if let ExclusionMethod::SingleStage = opts.exclusion {
|
||||||
state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes.", result.total_ballots, result.total_votes, dps=opts.pp_decimals));
|
if result.total_ballots == N::one() {
|
||||||
|
state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes.", result.total_votes, dps=opts.pp_decimals));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes.", result.total_ballots, result.total_votes, dps=opts.pp_decimals));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Transferring {:.0} ballot papers, totalling {:.dps$} votes, received at value {:.dps2$}.", result.total_ballots, result.total_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
if result.total_ballots == N::one() {
|
||||||
|
state.logger.log_literal(format!("Transferring 1 ballot, totalling {:.dps$} votes, received at value {:.dps2$}.", result.total_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
} else {
|
||||||
|
state.logger.log_literal(format!("Transferring {:.0} ballots, totalling {:.dps$} votes, received at value {:.dps2$}.", result.total_ballots, result.total_votes, value, dps=opts.pp_decimals, dps2=max(opts.pp_decimals, 2)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer candidate votes
|
// Transfer candidate votes
|
||||||
|
|
|
@ -211,7 +211,7 @@ fn recompute_keep_values<'s, N: Number>(state: &mut CountState<'s, N>, opts: &ST
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if the specified surpluses should be distributed, according to [STVOptions::meek_surplus_tolerance]
|
/// Determine if the specified surpluses should be distributed, according to [STVOptions::meek_surplus_tolerance]
|
||||||
fn should_distribute_surpluses<N: Number>(state: &CountState<N>, has_surplus: &Vec<&Candidate>, opts: &STVOptions) -> bool
|
fn should_distribute_surpluses<'a, N: Number>(state: &CountState<'a, N>, has_surplus: &Vec<&'a Candidate>, opts: &STVOptions) -> bool
|
||||||
where
|
where
|
||||||
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
for<'r> &'r N: ops::Sub<&'r N, Output=N>,
|
||||||
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
||||||
|
@ -220,14 +220,14 @@ where
|
||||||
// Distribute if any candidate has a surplus exceeding the tolerance
|
// Distribute if any candidate has a surplus exceeding the tolerance
|
||||||
let quota_tolerance = N::parse(&opts.meek_surplus_tolerance[0..opts.meek_surplus_tolerance.len()-1]) / N::from(100) + N::one();
|
let quota_tolerance = N::parse(&opts.meek_surplus_tolerance[0..opts.meek_surplus_tolerance.len()-1]) / N::from(100) + N::one();
|
||||||
return has_surplus.iter().any(|c| {
|
return has_surplus.iter().any(|c| {
|
||||||
let count_card = state.candidates.get(c).unwrap();
|
let count_card = &state.candidates[c];
|
||||||
return &count_card.votes / state.quota.as_ref().unwrap() > quota_tolerance;
|
return &count_card.votes / state.quota.as_ref().unwrap() > quota_tolerance;
|
||||||
});
|
});
|
||||||
} 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 = has_surplus.iter()
|
||||||
.fold(N::new(), |acc, c| acc + &state.candidates.get(c).unwrap().votes - state.quota.as_ref().unwrap());
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - state.quota.as_ref().unwrap());
|
||||||
return total_surpluses > quota_tolerance;
|
return total_surpluses > quota_tolerance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,7 +242,7 @@ where
|
||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
let mut has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
let mut has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
||||||
.filter(|c| {
|
.filter(|c| {
|
||||||
let count_card = state.candidates.get(c).unwrap();
|
let count_card = &state.candidates[c];
|
||||||
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -252,7 +252,7 @@ where
|
||||||
// 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 = has_surplus.iter()
|
||||||
.fold(N::new(), |acc, c| acc + &state.candidates.get(c).unwrap().votes - quota);
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
|
||||||
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);
|
||||||
|
@ -290,7 +290,7 @@ where
|
||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
has_surplus = state.election.candidates.iter()
|
has_surplus = state.election.candidates.iter()
|
||||||
.filter(|c| {
|
.filter(|c| {
|
||||||
let count_card = state.candidates.get(c).unwrap();
|
let count_card = &state.candidates[c];
|
||||||
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -300,7 +300,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 = has_surplus.iter()
|
||||||
.fold(N::new(), |acc, c| acc + &state.candidates.get(c).unwrap().votes - quota);
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes - quota);
|
||||||
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;
|
||||||
|
@ -311,7 +311,7 @@ where
|
||||||
// Recalculate transfers
|
// Recalculate transfers
|
||||||
let mut checksum = N::new();
|
let mut checksum = N::new();
|
||||||
for (candidate, count_card) in state.candidates.iter_mut() {
|
for (candidate, count_card) in state.candidates.iter_mut() {
|
||||||
count_card.transfers = &count_card.votes - &orig_candidates.get(candidate).unwrap().votes;
|
count_card.transfers = &count_card.votes - &orig_candidates[candidate].votes;
|
||||||
checksum += &count_card.transfers;
|
checksum += &count_card.transfers;
|
||||||
}
|
}
|
||||||
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
||||||
|
@ -339,7 +339,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let kv_str = state.election.candidates.iter()
|
let kv_str = state.election.candidates.iter()
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.map(|c| (c, &state.candidates[c]))
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
||||||
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
||||||
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
||||||
|
@ -363,14 +363,14 @@ where
|
||||||
let quota = state.quota.as_ref().unwrap();
|
let quota = state.quota.as_ref().unwrap();
|
||||||
let has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
let has_surplus: Vec<&Candidate> = state.election.candidates.iter()
|
||||||
.filter(|c| {
|
.filter(|c| {
|
||||||
let count_card = state.candidates.get(c).unwrap();
|
let count_card = &state.candidates[c];
|
||||||
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
return count_card.state == CandidateState::Elected && &count_card.votes > quota;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
recompute_keep_values(state, opts, &has_surplus);
|
recompute_keep_values(state, opts, &has_surplus);
|
||||||
|
|
||||||
let kv_str = state.election.candidates.iter()
|
let kv_str = state.election.candidates.iter()
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.map(|c| (c, &state.candidates[c]))
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
.filter(|(_, cc)| cc.state == CandidateState::Elected)
|
||||||
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
.sorted_unstable_by(|a, b| a.1.order_elected.cmp(&b.1.order_elected))
|
||||||
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
.map(|(c, cc)| format!("{} ({:.dps2$})", c.name, cc.keep_value.as_ref().unwrap(), dps2=max(opts.pp_decimals, 2)))
|
||||||
|
@ -400,7 +400,7 @@ where
|
||||||
// Recalculate transfers
|
// Recalculate transfers
|
||||||
let mut checksum = N::new();
|
let mut checksum = N::new();
|
||||||
for (candidate, count_card) in state.candidates.iter_mut() {
|
for (candidate, count_card) in state.candidates.iter_mut() {
|
||||||
count_card.transfers = &count_card.votes - &orig_candidates.get(candidate).unwrap().votes;
|
count_card.transfers = &count_card.votes - &orig_candidates[candidate].votes;
|
||||||
checksum += &count_card.transfers;
|
checksum += &count_card.transfers;
|
||||||
}
|
}
|
||||||
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
state.exhausted.transfers = &state.exhausted.votes - &orig_exhausted.votes;
|
||||||
|
|
118
src/stv/mod.rs
118
src/stv/mod.rs
|
@ -30,7 +30,7 @@ use crate::constraints;
|
||||||
use crate::numbers::Number;
|
use crate::numbers::Number;
|
||||||
use crate::election::{Candidate, CandidateState, CountCard, CountState, Vote};
|
use crate::election::{Candidate, CandidateState, CountCard, CountState, Vote};
|
||||||
use crate::sharandom::SHARandom;
|
use crate::sharandom::SHARandom;
|
||||||
use crate::ties::TieStrategy;
|
use crate::ties::{self, TieStrategy};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
@ -565,7 +565,7 @@ fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a
|
||||||
|
|
||||||
for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) {
|
for (i, preference) in vote.ballot.preferences.iter().enumerate().skip(vote.up_to_pref) {
|
||||||
let candidate = &state.election.candidates[*preference];
|
let candidate = &state.election.candidates[*preference];
|
||||||
let count_card = state.candidates.get(candidate).unwrap();
|
let count_card = &state.candidates[candidate];
|
||||||
|
|
||||||
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
|
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
|
||||||
next_candidate = Some(candidate);
|
next_candidate = Some(candidate);
|
||||||
|
@ -742,39 +742,28 @@ fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>, opts: &STVOption
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Declare elected all candidates meeting the quota
|
/// Declare elected all candidates meeting the quota
|
||||||
fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError> {
|
fn elect_meeting_quota<'a, N: Number>(state: &mut CountState<'a, N>, opts: &STVOptions) -> Result<bool, STVError> {
|
||||||
let vote_req = state.vote_required_election.as_ref().unwrap().clone(); // Have to do this or else the borrow checker gets confused
|
let vote_req = state.vote_required_election.as_ref().unwrap().clone(); // Have to do this or else the borrow checker gets confused
|
||||||
|
|
||||||
let mut cands_meeting_quota: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
|
let mut cands_meeting_quota: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.filter(|c| {
|
.map(|c| (c, &state.candidates[c]))
|
||||||
let cc = state.candidates.get(c).unwrap();
|
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_quota(&vote_req, cc, opts) })
|
||||||
return (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_quota(&vote_req, cc, opts);
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by votes
|
// Sort by votes
|
||||||
cands_meeting_quota.sort_unstable_by(|a, b| state.candidates.get(a).unwrap().votes.cmp(&state.candidates.get(b).unwrap().votes));
|
cands_meeting_quota.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
||||||
|
let mut cands_meeting_quota: Vec<&Candidate> = cands_meeting_quota.iter().map(|(c, _)| *c).collect();
|
||||||
|
|
||||||
let elected = !cands_meeting_quota.is_empty();
|
let elected = !cands_meeting_quota.is_empty();
|
||||||
|
|
||||||
while !cands_meeting_quota.is_empty() {
|
while !cands_meeting_quota.is_empty() {
|
||||||
// Declare elected in descending order of votes
|
// Declare elected in descending order of votes
|
||||||
let max_votes = cands_meeting_quota.iter()
|
let max_cands = ties::multiple_max_by(&cands_meeting_quota, |c| &state.candidates[c].votes);
|
||||||
.max_by(|a, b| state.candidates.get(**a).unwrap().votes.cmp(&state.candidates.get(**b).unwrap().votes))
|
let candidate = if max_cands.len() > 1 {
|
||||||
.unwrap();
|
choose_highest(state, opts, max_cands)?
|
||||||
let max_votes = &state.candidates.get(max_votes).unwrap().votes;
|
|
||||||
let max_cands_meeting_quota: Vec<&Candidate> = cands_meeting_quota.iter()
|
|
||||||
.filter(|c| &state.candidates.get(**c).unwrap().votes == max_votes)
|
|
||||||
.map(|c| *c)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let candidate;
|
|
||||||
if max_cands_meeting_quota.len() > 1 {
|
|
||||||
// Handle ties
|
|
||||||
candidate = choose_highest(state, opts, max_cands_meeting_quota)?;
|
|
||||||
} else {
|
} else {
|
||||||
candidate = max_cands_meeting_quota[0];
|
max_cands[0]
|
||||||
}
|
};
|
||||||
|
|
||||||
let count_card = state.candidates.get_mut(candidate).unwrap();
|
let count_card = state.candidates.get_mut(candidate).unwrap();
|
||||||
count_card.state = CandidateState::Elected;
|
count_card.state = CandidateState::Elected;
|
||||||
|
@ -788,10 +777,12 @@ fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
|
||||||
|
|
||||||
if constraints::update_constraints(state, opts) {
|
if constraints::update_constraints(state, opts) {
|
||||||
// Recheck as some candidates may have been doomed
|
// Recheck as some candidates may have been doomed
|
||||||
cands_meeting_quota = state.election.candidates.iter()
|
let mut cmq: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.filter(|c| { let cc = state.candidates.get(c).unwrap(); cc.state == CandidateState::Hopeful && meets_quota(&vote_req, cc, opts) })
|
.map(|c| (c, &state.candidates[c]))
|
||||||
|
.filter(|(_, cc)| { (cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded) && meets_quota(&vote_req, cc, opts) })
|
||||||
.collect();
|
.collect();
|
||||||
cands_meeting_quota.sort_unstable_by(|a, b| state.candidates.get(a).unwrap().votes.cmp(&state.candidates.get(b).unwrap().votes));
|
cmq.sort_unstable_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
||||||
|
cands_meeting_quota = cmq.iter().map(|(c, _)| *c).collect();
|
||||||
} else {
|
} else {
|
||||||
cands_meeting_quota.remove(cands_meeting_quota.iter().position(|c| *c == candidate).unwrap());
|
cands_meeting_quota.remove(cands_meeting_quota.iter().position(|c| *c == candidate).unwrap());
|
||||||
}
|
}
|
||||||
|
@ -835,7 +826,7 @@ where
|
||||||
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 = to_exclude.into_iter()
|
||||||
.fold(N::new(), |acc, c| acc + &state.candidates.get(c).unwrap().votes);
|
.fold(N::new(), |acc, c| acc + &state.candidates[c].votes);
|
||||||
if total_surpluses > &(&hopefuls[num_to_exclude + 1].1.votes - &total_excluded) {
|
if total_surpluses > &(&hopefuls[num_to_exclude + 1].1.votes - &total_excluded) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -866,7 +857,7 @@ where
|
||||||
fn bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError> {
|
fn bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result<bool, STVError> {
|
||||||
let mut hopefuls: Vec<&Candidate> = state.election.candidates.iter()
|
let mut hopefuls: Vec<&Candidate> = state.election.candidates.iter()
|
||||||
.filter(|c| {
|
.filter(|c| {
|
||||||
let cc = state.candidates.get(c).unwrap();
|
let cc = &state.candidates[c];
|
||||||
return cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded;
|
return cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -877,22 +868,12 @@ fn bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result
|
||||||
|
|
||||||
// Bulk elect all remaining candidates
|
// Bulk elect all remaining candidates
|
||||||
while !hopefuls.is_empty() {
|
while !hopefuls.is_empty() {
|
||||||
let max_votes = hopefuls.iter()
|
let max_cands = ties::multiple_max_by(&hopefuls, |c| &state.candidates[c].votes);
|
||||||
.max_by(|a, b| state.candidates.get(**a).unwrap().votes.cmp(&state.candidates.get(**b).unwrap().votes))
|
let candidate = if max_cands.len() > 1 {
|
||||||
.unwrap();
|
choose_highest(state, opts, max_cands)?
|
||||||
let max_votes = &state.candidates.get(max_votes).unwrap().votes;
|
|
||||||
let max_hopefuls: Vec<&Candidate> = hopefuls.iter()
|
|
||||||
.filter(|c| &state.candidates.get(**c).unwrap().votes == max_votes)
|
|
||||||
.map(|c| *c)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let candidate;
|
|
||||||
if max_hopefuls.len() > 1 {
|
|
||||||
// Handle ties
|
|
||||||
candidate = choose_highest(state, opts, max_hopefuls)?;
|
|
||||||
} else {
|
} else {
|
||||||
candidate = max_hopefuls[0];
|
max_cands[0]
|
||||||
}
|
};
|
||||||
|
|
||||||
let count_card = state.candidates.get_mut(candidate).unwrap();
|
let count_card = state.candidates.get_mut(candidate).unwrap();
|
||||||
count_card.state = CandidateState::Elected;
|
count_card.state = CandidateState::Elected;
|
||||||
|
@ -909,7 +890,7 @@ fn bulk_elect<N: Number>(state: &mut CountState<N>, opts: &STVOptions) -> Result
|
||||||
// Recheck as some candidates may have been doomed
|
// Recheck as some candidates may have been doomed
|
||||||
hopefuls = state.election.candidates.iter()
|
hopefuls = state.election.candidates.iter()
|
||||||
.filter(|c| {
|
.filter(|c| {
|
||||||
let cc = state.candidates.get(c).unwrap();
|
let cc = &state.candidates[c];
|
||||||
return cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded;
|
return cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded;
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -929,29 +910,22 @@ where
|
||||||
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
for<'r> &'r N: ops::Mul<&'r N, Output=N>,
|
||||||
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
for<'r> &'r N: ops::Div<&'r N, Output=N>,
|
||||||
{
|
{
|
||||||
let mut doomed: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
let doomed: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.filter(|c| state.candidates[c].state == CandidateState::Doomed)
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::Doomed)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !doomed.is_empty() {
|
if !doomed.is_empty() {
|
||||||
let excluded_candidates;
|
let excluded_candidates;
|
||||||
|
|
||||||
if opts.bulk_exclude {
|
if opts.bulk_exclude {
|
||||||
excluded_candidates = doomed.into_iter().map(|(c, _)| c).collect();
|
excluded_candidates = doomed;
|
||||||
} else {
|
} else {
|
||||||
// Exclude only the lowest-ranked doomed candidate
|
// Exclude only the lowest-ranked doomed candidate
|
||||||
// Sort by votes
|
let min_cands = ties::multiple_min_by(&doomed, |c| &state.candidates[c].votes);
|
||||||
doomed.sort_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
excluded_candidates = if min_cands.len() > 1 {
|
||||||
|
vec![choose_lowest(state, opts, min_cands)?]
|
||||||
// Handle ties
|
|
||||||
if doomed.len() > 1 && doomed[0].1.votes == doomed[1].1.votes {
|
|
||||||
let min_votes = &doomed[0].1.votes;
|
|
||||||
let doomed = doomed.into_iter().filter_map(|(c, cc)| if &cc.votes == min_votes { Some(c) } else { None }).collect();
|
|
||||||
excluded_candidates = vec![choose_lowest(state, opts, doomed)?];
|
|
||||||
} else {
|
} else {
|
||||||
excluded_candidates = vec![&doomed[0].0];
|
vec![min_cands[0]]
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
|
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
|
||||||
|
@ -1028,22 +1002,16 @@ where
|
||||||
|
|
||||||
// Exclude lowest ranked candidate
|
// Exclude lowest ranked candidate
|
||||||
if excluded_candidates.is_empty() {
|
if excluded_candidates.is_empty() {
|
||||||
let mut hopefuls: Vec<(&Candidate, &CountCard<N>)> = state.election.candidates.iter() // Present in order in case of tie
|
let hopefuls: Vec<&Candidate> = state.election.candidates.iter() // Present in order in case of tie
|
||||||
.map(|c| (c, state.candidates.get(c).unwrap()))
|
.filter(|c| state.candidates[c].state == CandidateState::Hopeful)
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::Hopeful)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by votes
|
let min_cands = ties::multiple_min_by(&hopefuls, |c| &state.candidates[c].votes);
|
||||||
hopefuls.sort_by(|a, b| a.1.votes.cmp(&b.1.votes));
|
excluded_candidates = if min_cands.len() > 1 {
|
||||||
|
vec![choose_lowest(state, opts, min_cands)?]
|
||||||
// Handle ties
|
|
||||||
if hopefuls.len() > 1 && hopefuls[0].1.votes == hopefuls[1].1.votes {
|
|
||||||
let min_votes = &hopefuls[0].1.votes;
|
|
||||||
let hopefuls = hopefuls.into_iter().filter_map(|(c, cc)| if &cc.votes == min_votes { Some(c) } else { None }).collect();
|
|
||||||
excluded_candidates = vec![choose_lowest(state, opts, hopefuls)?];
|
|
||||||
} else {
|
} else {
|
||||||
excluded_candidates = vec![&hopefuls[0].0];
|
vec![min_cands[0]]
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
|
let names: Vec<&str> = excluded_candidates.iter().map(|c| c.name.as_str()).sorted().collect();
|
||||||
|
@ -1278,9 +1246,9 @@ fn update_tiebreaks<N: Number>(state: &mut CountState<N>, _opts: &STVOptions) {
|
||||||
} else {
|
} else {
|
||||||
// Tied in this round - refer to last round
|
// Tied in this round - refer to last round
|
||||||
let mut tied_this_round: Vec<&Candidate> = group.into_iter().map(|c| *c).collect();
|
let mut tied_this_round: Vec<&Candidate> = group.into_iter().map(|c| *c).collect();
|
||||||
tied_this_round.sort_unstable_by(|a, b| hm_orig.get(a).unwrap().cmp(hm_orig.get(b).unwrap()));
|
tied_this_round.sort_unstable_by(|a, b| hm_orig[a].cmp(&hm_orig[b]));
|
||||||
let tied_this_round = tied_this_round.into_iter()
|
let tied_this_round = tied_this_round.into_iter()
|
||||||
.group_by(|c| hm_orig.get(c).unwrap());
|
.group_by(|c| hm_orig[c]);
|
||||||
|
|
||||||
for (_, group2) in tied_this_round.into_iter() {
|
for (_, group2) in tied_this_round.into_iter() {
|
||||||
for candidate in group2 {
|
for candidate in group2 {
|
||||||
|
|
|
@ -314,7 +314,7 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
|
||||||
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.kind.unwrap_or("")).into());
|
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.kind.unwrap_or("")).into());
|
||||||
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.title).into());
|
result.push(&format!(r#"<td{}>{}</td>"#, tdclasses1, state.title).into());
|
||||||
for candidate in state.election.candidates.iter() {
|
for candidate in state.election.candidates.iter() {
|
||||||
let count_card = state.candidates.get(candidate).unwrap();
|
let count_card = &state.candidates[candidate];
|
||||||
match count_card.state {
|
match count_card.state {
|
||||||
CandidateState::Hopeful | CandidateState::Guarded => {
|
CandidateState::Hopeful | CandidateState::Guarded => {
|
||||||
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
|
result.push(&format!(r#"<td class="{}count">{}</td>"#, tdclasses2, pp(&count_card.transfers, opts.pp_decimals)).into());
|
||||||
|
@ -377,7 +377,7 @@ fn finalise_results_table<N: Number>(state: &CountState<N>) -> Array {
|
||||||
|
|
||||||
// Candidate states
|
// Candidate states
|
||||||
for candidate in state.election.candidates.iter() {
|
for candidate in state.election.candidates.iter() {
|
||||||
let count_card = state.candidates.get(candidate).unwrap();
|
let count_card = &state.candidates[candidate];
|
||||||
if count_card.state == stv::CandidateState::Elected {
|
if count_card.state == stv::CandidateState::Elected {
|
||||||
result.push(&format!(r#"<td rowspan="2" class="bb elected">ELECTED {}</td>"#, count_card.order_elected).into());
|
result.push(&format!(r#"<td rowspan="2" class="bb elected">ELECTED {}</td>"#, count_card.order_elected).into());
|
||||||
} else if count_card.state == stv::CandidateState::Excluded {
|
} else if count_card.state == stv::CandidateState::Excluded {
|
||||||
|
@ -403,7 +403,7 @@ fn final_result_summary<N: Number>(state: &CountState<N>, opts: &stv::STVOptions
|
||||||
winners.push((candidate, count_card.order_elected, &count_card.keep_value));
|
winners.push((candidate, count_card.order_elected, &count_card.keep_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
winners.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
|
winners.sort_unstable_by(|a, b| a.1.cmp(&b.1));
|
||||||
|
|
||||||
for (winner, _, kv_opt) in winners.into_iter() {
|
for (winner, _, kv_opt) in winners.into_iter() {
|
||||||
if let Some(kv) = kv_opt {
|
if let Some(kv) = kv_opt {
|
||||||
|
|
82
src/ties.rs
82
src/ties.rs
|
@ -59,10 +59,10 @@ impl TieStrategy {
|
||||||
let mut candidates = candidates.clone();
|
let mut candidates = candidates.clone();
|
||||||
candidates.sort_unstable_by(|a, b|
|
candidates.sort_unstable_by(|a, b|
|
||||||
// Compare b to a to sort high-to-low
|
// Compare b to a to sort high-to-low
|
||||||
state.forwards_tiebreak.as_ref().unwrap().get(b).unwrap()
|
state.forwards_tiebreak.as_ref().unwrap()[b]
|
||||||
.cmp(&state.forwards_tiebreak.as_ref().unwrap().get(a).unwrap())
|
.cmp(&state.forwards_tiebreak.as_ref().unwrap()[a])
|
||||||
);
|
);
|
||||||
if state.forwards_tiebreak.as_ref().unwrap().get(candidates[0]).unwrap() == state.forwards_tiebreak.as_ref().unwrap().get(candidates[1]).unwrap() {
|
if state.forwards_tiebreak.as_ref().unwrap()[candidates[0]] == state.forwards_tiebreak.as_ref().unwrap()[candidates[1]] {
|
||||||
return Err(STVError::UnresolvedTie);
|
return Err(STVError::UnresolvedTie);
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Tie between {} broken forwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
state.logger.log_literal(format!("Tie between {} broken forwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
||||||
|
@ -72,10 +72,10 @@ impl TieStrategy {
|
||||||
Self::Backwards => {
|
Self::Backwards => {
|
||||||
let mut candidates = candidates.clone();
|
let mut candidates = candidates.clone();
|
||||||
candidates.sort_unstable_by(|a, b|
|
candidates.sort_unstable_by(|a, b|
|
||||||
state.backwards_tiebreak.as_ref().unwrap().get(b).unwrap()
|
state.backwards_tiebreak.as_ref().unwrap()[b]
|
||||||
.cmp(&state.backwards_tiebreak.as_ref().unwrap().get(a).unwrap())
|
.cmp(&state.backwards_tiebreak.as_ref().unwrap()[a])
|
||||||
);
|
);
|
||||||
if state.backwards_tiebreak.as_ref().unwrap().get(candidates[0]).unwrap() == state.backwards_tiebreak.as_ref().unwrap().get(candidates[1]).unwrap() {
|
if state.backwards_tiebreak.as_ref().unwrap()[candidates[0]] == state.backwards_tiebreak.as_ref().unwrap()[candidates[1]] {
|
||||||
return Err(STVError::UnresolvedTie);
|
return Err(STVError::UnresolvedTie);
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Tie between {} broken backwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
state.logger.log_literal(format!("Tie between {} broken backwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
||||||
|
@ -106,10 +106,10 @@ impl TieStrategy {
|
||||||
Self::Forwards => {
|
Self::Forwards => {
|
||||||
let mut candidates = candidates.clone();
|
let mut candidates = candidates.clone();
|
||||||
candidates.sort_unstable_by(|a, b|
|
candidates.sort_unstable_by(|a, b|
|
||||||
state.forwards_tiebreak.as_ref().unwrap().get(a).unwrap()
|
state.forwards_tiebreak.as_ref().unwrap()[a]
|
||||||
.cmp(&state.forwards_tiebreak.as_ref().unwrap().get(b).unwrap())
|
.cmp(&state.forwards_tiebreak.as_ref().unwrap()[b])
|
||||||
);
|
);
|
||||||
if state.forwards_tiebreak.as_ref().unwrap().get(candidates[0]).unwrap() == state.forwards_tiebreak.as_ref().unwrap().get(candidates[1]).unwrap() {
|
if state.forwards_tiebreak.as_ref().unwrap()[candidates[0]] == state.forwards_tiebreak.as_ref().unwrap()[candidates[1]] {
|
||||||
return Err(STVError::UnresolvedTie);
|
return Err(STVError::UnresolvedTie);
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Tie between {} broken forwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
state.logger.log_literal(format!("Tie between {} broken forwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
||||||
|
@ -119,10 +119,10 @@ impl TieStrategy {
|
||||||
Self::Backwards => {
|
Self::Backwards => {
|
||||||
let mut candidates = candidates.clone();
|
let mut candidates = candidates.clone();
|
||||||
candidates.sort_unstable_by(|a, b|
|
candidates.sort_unstable_by(|a, b|
|
||||||
state.backwards_tiebreak.as_ref().unwrap().get(a).unwrap()
|
state.backwards_tiebreak.as_ref().unwrap()[a]
|
||||||
.cmp(&state.backwards_tiebreak.as_ref().unwrap().get(b).unwrap())
|
.cmp(&state.backwards_tiebreak.as_ref().unwrap()[b])
|
||||||
);
|
);
|
||||||
if state.backwards_tiebreak.as_ref().unwrap().get(candidates[0]).unwrap() == state.backwards_tiebreak.as_ref().unwrap().get(candidates[1]).unwrap() {
|
if state.backwards_tiebreak.as_ref().unwrap()[candidates[0]] == state.backwards_tiebreak.as_ref().unwrap()[candidates[1]] {
|
||||||
return Err(STVError::UnresolvedTie);
|
return Err(STVError::UnresolvedTie);
|
||||||
} else {
|
} else {
|
||||||
state.logger.log_literal(format!("Tie between {} broken backwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
state.logger.log_literal(format!("Tie between {} broken backwards.", smart_join(&candidates.iter().map(|c| c.name.as_str()).collect())));
|
||||||
|
@ -139,6 +139,64 @@ impl TieStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return all maximal items according to the given key
|
||||||
|
pub fn multiple_max_by<E: Copy, K, C: Ord>(items: &Vec<E>, key: K) -> Vec<E>
|
||||||
|
where
|
||||||
|
K: Fn(&E) -> C
|
||||||
|
{
|
||||||
|
let mut max_key = None;
|
||||||
|
let mut max_items = Vec::new();
|
||||||
|
for item in items.iter() {
|
||||||
|
let item_key = key(item);
|
||||||
|
match &max_key {
|
||||||
|
Some(v) => {
|
||||||
|
if &item_key > v {
|
||||||
|
max_key = Some(item_key);
|
||||||
|
max_items.clear();
|
||||||
|
max_items.push(*item);
|
||||||
|
} else if &item_key == v {
|
||||||
|
max_items.push(*item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
max_key = Some(item_key);
|
||||||
|
max_items.clear();
|
||||||
|
max_items.push(*item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max_items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all minimal items according to the given key
|
||||||
|
pub fn multiple_min_by<E: Copy, K, C: Ord>(items: &Vec<E>, key: K) -> Vec<E>
|
||||||
|
where
|
||||||
|
K: Fn(&E) -> C
|
||||||
|
{
|
||||||
|
let mut min_key = None;
|
||||||
|
let mut min_items = Vec::new();
|
||||||
|
for item in items.iter() {
|
||||||
|
let item_key = key(item);
|
||||||
|
match &min_key {
|
||||||
|
Some(v) => {
|
||||||
|
if &item_key < v {
|
||||||
|
min_key = Some(item_key);
|
||||||
|
min_items.clear();
|
||||||
|
min_items.push(*item);
|
||||||
|
} else if &item_key == v {
|
||||||
|
min_items.push(*item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
min_key = Some(item_key);
|
||||||
|
min_items.clear();
|
||||||
|
min_items.push(*item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return min_items;
|
||||||
|
}
|
||||||
|
|
||||||
/// Prompt the candidate for input, depending on CLI or WebAssembly target
|
/// Prompt the candidate for input, depending on CLI or WebAssembly target
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> {
|
fn prompt<'c>(candidates: &Vec<&'c Candidate>) -> Result<&'c Candidate, STVError> {
|
||||||
|
|
|
@ -106,7 +106,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut candidate_votes: Vec<Option<N>> = records.iter().skip(2)
|
let mut candidate_votes: Vec<Option<N>> = records.iter().skip(2)
|
||||||
.map(|r| if r[idx*2 + 1].len() > 0 { Some(N::from(r[idx*2 + 1].parse::<f64>().expect("Syntax Error"))) } else { None })
|
.map(|r| if r[idx*2 + 1].len() > 0 { Some(N::parse(&r[idx*2 + 1])) } else { None })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Validate exhausted/LBF
|
// Validate exhausted/LBF
|
||||||
|
|
Loading…
Reference in New Issue