Handle withdrawn candidates
This commit is contained in:
parent
59539d807a
commit
4c4099ee22
|
@ -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,
|
||||
}
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,7 +959,7 @@ 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)
|
||||
.filter(|(_, cc)| cc.state == CandidateState::Hopeful)
|
||||
.collect();
|
||||
|
||||
// Sort by votes
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue