Handle withdrawn candidates

This commit is contained in:
RunasSudo 2021-06-12 00:50:01 +10:00
parent 59539d807a
commit 4c4099ee22
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 64 additions and 38 deletions

View File

@ -24,6 +24,7 @@ pub struct Election<N> {
pub name: String,
pub seats: usize,
pub candidates: Vec<Candidate>,
pub withdrawn_candidates: Vec<usize>,
pub ballots: Vec<Ballot<N>>,
}
@ -40,6 +41,7 @@ impl<N: Number> Election<N> {
name: String::new(),
seats: seats,
candidates: Vec::with_capacity(num_candidates),
withdrawn_candidates: Vec::new(),
ballots: Vec::new(),
};
@ -50,6 +52,16 @@ impl<N: Number> Election<N> {
}
let mut bits = line.split(" ");
if line.starts_with("-") {
// Withdrawn candidates
for bit in bits.into_iter() {
let val = bit[1..bit.len()].parse::<usize>().expect("Syntax Error");
election.withdrawn_candidates.push(val - 1);
}
continue;
}
let value = N::parse(bits.next().expect("Syntax Error"));
let mut ballot = Ballot {
@ -150,6 +162,10 @@ impl<'a, N: Number> CountState<'a, N> {
state.candidates.insert(candidate, CountCard::new());
}
for withdrawn_idx in election.withdrawn_candidates.iter() {
state.candidates.get_mut(&election.candidates[*withdrawn_idx]).unwrap().state = CandidateState::Withdrawn;
}
return state;
}
@ -203,7 +219,7 @@ pub struct CountCard<'a, N> {
impl<'a, N: Number> CountCard<'a, N> {
pub fn new() -> Self {
return CountCard {
state: CandidateState::HOPEFUL,
state: CandidateState::Hopeful,
order_elected: 0,
orig_votes: N::new(),
transfers: N::new(),
@ -245,9 +261,10 @@ pub struct Ballot<N> {
#[derive(PartialEq)]
#[derive(Clone)]
pub enum CandidateState {
HOPEFUL,
GUARDED,
ELECTED,
DOOMED,
EXCLUDED,
Hopeful,
Guarded,
Elected,
Doomed,
Withdrawn,
Excluded,
}

View File

@ -219,7 +219,7 @@ where
let mut winners = Vec::new();
for (candidate, count_card) in state.candidates.iter() {
if count_card.state == CandidateState::ELECTED {
if count_card.state == CandidateState::Elected {
winners.push((candidate, count_card.order_elected));
}
}
@ -232,13 +232,17 @@ where
fn print_candidates<'a, N: 'a + Number, I: Iterator<Item=(&'a Candidate, &'a CountCard<'a, N>)>>(candidates: I, cmd_opts: &STV) {
for (candidate, count_card) in candidates {
if count_card.state == CandidateState::ELECTED {
if count_card.state == CandidateState::Elected {
println!("- {}: {:.dps$} ({:.dps$}) - ELECTED {}", candidate.name, count_card.votes, count_card.transfers, count_card.order_elected, dps=cmd_opts.pp_decimals);
} else if count_card.state == CandidateState::EXCLUDED {
} else if count_card.state == CandidateState::Excluded {
// If --hide-excluded, hide unless nonzero votes or nonzero transfers
if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() {
println!("- {}: {:.dps$} ({:.dps$}) - Excluded {}", candidate.name, count_card.votes, count_card.transfers, -count_card.order_elected, dps=cmd_opts.pp_decimals);
}
} else if count_card.state == CandidateState::Withdrawn {
if !cmd_opts.hide_excluded || !count_card.votes.is_zero() || !count_card.transfers.is_zero() {
println!("- {}: {:.dps$} ({:.dps$}) - Withdrawn", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals);
}
} else {
println!("- {}: {:.dps$} ({:.dps$})", candidate.name, count_card.votes, count_card.transfers, dps=cmd_opts.pp_decimals);
}

View File

@ -349,7 +349,7 @@ fn next_preferences<'a, N: Number>(state: &CountState<'a, N>, votes: Vec<Vote<'a
let candidate = &state.election.candidates[*preference];
let count_card = state.candidates.get(candidate).unwrap();
if let CandidateState::HOPEFUL | CandidateState::GUARDED = count_card.state {
if let CandidateState::Hopeful | CandidateState::Guarded = count_card.state {
next_candidate = Some(candidate);
vote.up_to_pref = i + 1;
break;
@ -486,7 +486,7 @@ fn calculate_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions) {
// Calculate total active vote
let total_active_vote = state.candidates.values().fold(N::zero(), |acc, cc| {
match cc.state {
CandidateState::ELECTED => { if &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } }
CandidateState::Elected => { if &cc.votes > state.quota.as_ref().unwrap() { acc + &cc.votes - state.quota.as_ref().unwrap() } else { acc } }
_ => { acc + &cc.votes }
}
});
@ -535,7 +535,7 @@ fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
let vote_req = state.vote_required_election.as_ref().unwrap(); // Have to do this or else the borrow checker gets confused
let mut cands_meeting_quota: Vec<&Candidate> = state.election.candidates.iter()
.filter(|c| { let cc = state.candidates.get(c).unwrap(); cc.state == CandidateState::HOPEFUL && meets_quota(vote_req, cc, opts) })
.filter(|c| { let cc = state.candidates.get(c).unwrap(); cc.state == CandidateState::Hopeful && meets_quota(vote_req, cc, opts) })
.collect();
if cands_meeting_quota.len() > 0 {
@ -545,7 +545,7 @@ fn elect_meeting_quota<N: Number>(state: &mut CountState<N>, opts: &STVOptions)
// Declare elected in descending order of votes
for candidate in cands_meeting_quota.into_iter().rev() {
let count_card = state.candidates.get_mut(candidate).unwrap();
count_card.state = CandidateState::ELECTED;
count_card.state = CandidateState::Elected;
state.num_elected += 1;
count_card.order_elected = state.num_elected as isize;
state.logger.log_smart(
@ -574,7 +574,7 @@ where
{
// Do not defer if this could change the last 2 candidates
let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL || cc.state == CandidateState::GUARDED)
.filter(|(_, cc)| cc.state == CandidateState::Hopeful || cc.state == CandidateState::Guarded)
.collect();
hopefuls.sort_unstable_by(|(_, cc1), (_, cc2)| cc1.votes.cmp(&cc2.votes));
if total_surpluses > &(&hopefuls[1].1.votes - &hopefuls[0].1.votes) {
@ -613,7 +613,7 @@ where
// Determine if surplues can be deferred
if opts.defer_surpluses {
if can_defer_surpluses(state, opts, &total_surpluses) {
state.logger.log_literal(format!("Distribution of surpluses totalling {:.2} votes will be deferred.", total_surpluses));
state.logger.log_literal(format!("Distribution of surpluses totalling {:.dps$} votes will be deferred.", total_surpluses, dps=opts.pp_decimals));
return false;
}
}
@ -878,14 +878,14 @@ fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
// Bulk elect all remaining candidates
let mut hopefuls: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
.filter(|(_, cc)| cc.state == CandidateState::Hopeful)
.collect();
// TODO: Handle ties
hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
for (candidate, count_card) in hopefuls.into_iter() {
count_card.state = CandidateState::ELECTED;
count_card.state = CandidateState::Elected;
state.num_elected += 1;
count_card.order_elected = state.num_elected as isize;
@ -905,7 +905,7 @@ fn hopefuls_to_bulk_exclude<'a, N: Number>(state: &CountState<'a, N>, _opts: &ST
let mut excluded_candidates = Vec::new();
let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
.filter(|(_, cc)| cc.state == CandidateState::Hopeful)
.collect();
// Sort by votes
@ -959,8 +959,8 @@ where
// Exclude lowest ranked candidate
if excluded_candidates.len() == 0 {
let mut hopefuls: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
.collect();
.filter(|(_, cc)| cc.state == CandidateState::Hopeful)
.collect();
// Sort by votes
// TODO: Handle ties
@ -990,8 +990,8 @@ where
{
// Cannot filter by raw vote count, as candidates may have 0.00 votes but still have recorded ballot papers
let mut excluded_with_votes: Vec<(&&Candidate, &CountCard<N>)> = state.candidates.iter()
//.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && !cc.votes.is_zero())
.filter(|(_, cc)| cc.state == CandidateState::EXCLUDED && cc.parcels.iter().any(|p| p.len() > 0))
//.filter(|(_, cc)| cc.state == CandidateState::Excluded && !cc.votes.is_zero())
.filter(|(_, cc)| cc.state == CandidateState::Excluded && cc.parcels.iter().any(|p| p.len() > 0))
.collect();
if excluded_with_votes.len() > 0 {
@ -1031,8 +1031,8 @@ where
let count_card = state.candidates.get_mut(excluded_candidate).unwrap();
// Rust borrow checker is unhappy if we try to put this in exclude_hopefuls ??!
if count_card.state != CandidateState::EXCLUDED {
count_card.state = CandidateState::EXCLUDED;
if count_card.state != CandidateState::Excluded {
count_card.state = CandidateState::Excluded;
state.num_excluded += 1;
count_card.order_elected = -(order_excluded as isize);
}

View File

@ -165,16 +165,19 @@ fn update_results_table<N: Number>(stage_num: usize, state: &CountState<N>, opts
result.push(&format!(r#"<td>{}</td>"#, state.title).into());
for candidate in state.election.candidates.iter() {
let count_card = state.candidates.get(candidate).unwrap();
if count_card.state == stv::CandidateState::ELECTED {
if count_card.state == stv::CandidateState::Elected {
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count elected">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
} else if count_card.state == stv::CandidateState::EXCLUDED {
} else if count_card.state == stv::CandidateState::Excluded {
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
if count_card.votes.is_zero() {
result.push(&r#"<td class="count excluded">Ex</td>"#.into());
} else {
result.push(&format!(r#"<td class="count excluded">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
}
} else if count_card.state == stv::CandidateState::Withdrawn {
result.push(&r#"<td class="count excluded"></td>"#.into());
result.push(&r#"<td class="count excluded">WD</td>"#.into());
} else {
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.transfers, opts.pp_decimals)).into());
result.push(&format!(r#"<td class="count">{}</td>"#, pp(&count_card.votes, opts.pp_decimals)).into());
@ -214,10 +217,12 @@ fn finalise_results_table<N: Number>(state: &CountState<N>) -> Array {
// Candidate states
for candidate in state.election.candidates.iter() {
let count_card = state.candidates.get(candidate).unwrap();
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());
} else if count_card.state == stv::CandidateState::EXCLUDED {
} else if count_card.state == stv::CandidateState::Excluded {
result.push(&format!(r#"<td rowspan="2" class="bb excluded">Excluded {}</td>"#, -count_card.order_elected).into());
} else if count_card.state == stv::CandidateState::Withdrawn {
result.push(&r#"<td rowspan="2" class="bb excluded">Withdrawn</td>"#.into());
} else {
result.push(&r#"<td rowspan="2" class="bb"></td>"#.into());
}
@ -232,7 +237,7 @@ fn final_result_summary<N: Number>(state: &CountState<N>) -> String {
let mut winners = Vec::new();
for (candidate, count_card) in state.candidates.iter() {
if count_card.state == CandidateState::ELECTED {
if count_card.state == CandidateState::Elected {
winners.push((candidate, count_card.order_elected));
}
}

View File

@ -123,11 +123,11 @@ fn ers97_rational() {
for (candidate, candidate_state) in state.election.candidates.iter().zip(candidate_states) {
let count_card = state.candidates.get(candidate).unwrap();
if candidate_state == "" {
assert!(count_card.state == CandidateState::HOPEFUL);
assert!(count_card.state == CandidateState::Hopeful);
} else if candidate_state == "EL" || candidate_state == "PEL" {
assert!(count_card.state == CandidateState::ELECTED);
assert!(count_card.state == CandidateState::Elected);
} else if candidate_state == "EX" || candidate_state == "EXCLUDING" {
assert!(count_card.state == CandidateState::EXCLUDED);
assert!(count_card.state == CandidateState::Excluded);
} else {
panic!("Unknown state descriptor {}", candidate_state);
}

View File

@ -109,11 +109,11 @@ fn scotland_linn07_fixed5() {
.get_text().unwrap()
.to_string();
if cand_state == "Continuing" {
assert!(count_card.state == CandidateState::HOPEFUL);
assert!(count_card.state == CandidateState::Hopeful);
} else if cand_state == "Elected" {
assert!(count_card.state == CandidateState::ELECTED);
assert!(count_card.state == CandidateState::Elected);
} else if cand_state == "Excluded" {
assert!(count_card.state == CandidateState::EXCLUDED);
assert!(count_card.state == CandidateState::Excluded);
} else {
panic!("Unknown state descriptor {}", cand_state);
}

View File

@ -104,11 +104,11 @@ fn validate_stage<N: Number>(idx: usize, state: &CountState<N>, records: &Vec<St
for (candidate, candidate_state) in state.election.candidates.iter().zip(candidate_states) {
let count_card = state.candidates.get(candidate).unwrap();
if candidate_state == "" {
assert!(count_card.state == CandidateState::HOPEFUL);
assert!(count_card.state == CandidateState::Hopeful);
} else if candidate_state == "EL" || candidate_state == "PEL" {
assert!(count_card.state == CandidateState::ELECTED);
assert!(count_card.state == CandidateState::Elected);
} else if candidate_state == "EX" || candidate_state == "EXCLUDING" {
assert!(count_card.state == CandidateState::EXCLUDED);
assert!(count_card.state == CandidateState::Excluded);
} else {
panic!("Unknown state descriptor {}", candidate_state);
}