Add logs during count
This commit is contained in:
parent
df69ef456f
commit
91190c7e26
@ -97,6 +97,10 @@ pub struct CountState<'a, N> {
|
||||
|
||||
pub num_elected: usize,
|
||||
pub num_excluded: usize,
|
||||
|
||||
pub kind: Option<&'a str>,
|
||||
pub title: String,
|
||||
pub logs: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a, N: Number> CountState<'a, N> {
|
||||
@ -109,6 +113,9 @@ impl<'a, N: Number> CountState<'a, N> {
|
||||
quota: N::new(),
|
||||
num_elected: 0,
|
||||
num_excluded: 0,
|
||||
kind: None,
|
||||
title: String::new(),
|
||||
logs: Vec::new(),
|
||||
};
|
||||
|
||||
for candidate in election.candidates.iter() {
|
||||
@ -127,12 +134,17 @@ impl<'a, N: Number> CountState<'a, N> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum CountStateOrRef<'a, N> {
|
||||
State(CountState<'a, N>),
|
||||
State(CountState<'a, N>), // NYI: May be used e.g. for tie-breaking or rollback-based constraints
|
||||
Ref(&'a CountState<'a, N>),
|
||||
}
|
||||
|
||||
impl<'a, N> CountStateOrRef<'a, N> {
|
||||
pub fn from(state: &'a CountState<N>) -> Self {
|
||||
return Self::Ref(state);
|
||||
}
|
||||
|
||||
pub fn as_ref(&self) -> &CountState<N> {
|
||||
match self {
|
||||
CountStateOrRef::State(state) => &state,
|
||||
@ -142,8 +154,9 @@ impl<'a, N> CountStateOrRef<'a, N> {
|
||||
}
|
||||
|
||||
pub struct StageResult<'a, N> {
|
||||
pub title: &'a str,
|
||||
pub logs: Vec<&'a str>,
|
||||
pub kind: Option<&'a str>,
|
||||
pub title: &'a String,
|
||||
pub logs: &'a Vec<String>,
|
||||
pub state: CountStateOrRef<'a, N>,
|
||||
}
|
||||
|
||||
|
55
src/main.rs
55
src/main.rs
@ -76,7 +76,10 @@ 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) {
|
||||
// Print stage details
|
||||
println!("{}. {}", stage_num, result.title);
|
||||
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.as_ref();
|
||||
@ -106,9 +109,17 @@ fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>, cmd_opts: &
|
||||
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.logs,
|
||||
state: CountStateOrRef::from(&state),
|
||||
};
|
||||
print_stage(stage_num, &result, &cmd_opts);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let should_clone_state = false;
|
||||
|
||||
// Read arguments
|
||||
let opts: Opts = Opts::parse();
|
||||
let Command::STV(cmd_opts) = opts.command;
|
||||
@ -127,17 +138,11 @@ fn main() {
|
||||
stv::elect_meeting_quota(&mut state);
|
||||
|
||||
// Display
|
||||
// TODO: Add logs during count
|
||||
let result = StageResult {
|
||||
title: "First preferences",
|
||||
logs: vec!["First preferences distributed."],
|
||||
state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) },
|
||||
};
|
||||
print_stage(1, &result, &cmd_opts);
|
||||
|
||||
let mut stage_num = 1;
|
||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||
|
||||
loop {
|
||||
state.logs.clear();
|
||||
state.step_all();
|
||||
stage_num += 1;
|
||||
|
||||
@ -149,48 +154,28 @@ fn main() {
|
||||
// Continue exclusions
|
||||
if stv::continue_exclusion(&mut state) {
|
||||
stv::elect_meeting_quota(&mut state);
|
||||
let result = StageResult {
|
||||
title: "Exclusion",
|
||||
logs: vec!["Continuing exclusion."],
|
||||
state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) },
|
||||
};
|
||||
print_stage(stage_num, &result, &cmd_opts);
|
||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Distribute surpluses
|
||||
if stv::distribute_surpluses(&mut state) {
|
||||
stv::elect_meeting_quota(&mut state);
|
||||
let result = StageResult {
|
||||
title: "Surplus",
|
||||
logs: vec!["Surplus distributed."],
|
||||
state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) },
|
||||
};
|
||||
print_stage(stage_num, &result, &cmd_opts);
|
||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt bulk election
|
||||
if stv::bulk_elect(&mut state) {
|
||||
stv::elect_meeting_quota(&mut state);
|
||||
let result = StageResult {
|
||||
title: "Bulk election",
|
||||
logs: vec!["Bulk election."],
|
||||
state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) },
|
||||
};
|
||||
print_stage(stage_num, &result, &cmd_opts);
|
||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude lowest hopeful
|
||||
if stv::exclude_hopefuls(&mut state) {
|
||||
stv::elect_meeting_quota(&mut state);
|
||||
let result = StageResult {
|
||||
title: "Exclusion",
|
||||
logs: vec!["Candidate excluded."],
|
||||
state: if should_clone_state { CountStateOrRef::State(state.clone()) } else { CountStateOrRef::Ref(&state) },
|
||||
};
|
||||
print_stage(stage_num, &result, &cmd_opts);
|
||||
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -109,11 +109,18 @@ pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) {
|
||||
let parcel = result.exhausted.votes as Parcel<N>;
|
||||
state.exhausted.parcels.push(parcel);
|
||||
state.exhausted.transfer(&result.exhausted.num_votes);
|
||||
|
||||
state.kind = None;
|
||||
state.title = "First preferences".to_string();
|
||||
state.logs.push("First preferences distributed.".to_string());
|
||||
}
|
||||
|
||||
pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
|
||||
let mut log = String::new();
|
||||
|
||||
// Calculate the total vote
|
||||
state.quota = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
|
||||
log.push_str(format!("{:.2} usable votes, so the quota is ", state.quota).as_str());
|
||||
|
||||
// TODO: Different quotas
|
||||
state.quota /= N::from(state.election.seats + 1);
|
||||
@ -121,6 +128,9 @@ pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
|
||||
// TODO: Different rounding rules
|
||||
state.quota += N::one();
|
||||
state.quota.floor_mut();
|
||||
log.push_str(format!("{:.2}.", state.quota).as_str());
|
||||
|
||||
state.logs.push(log);
|
||||
}
|
||||
|
||||
fn meets_quota<N: Number>(quota: &N, count_card: &CountCard<N>) -> bool {
|
||||
@ -139,11 +149,12 @@ pub fn elect_meeting_quota<N: Number>(state: &mut CountState<N>) {
|
||||
cands_meeting_quota.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
|
||||
|
||||
// Declare elected in descending order of votes
|
||||
for (_, count_card) in cands_meeting_quota.into_iter().rev() {
|
||||
for (candidate, count_card) in cands_meeting_quota.into_iter().rev() {
|
||||
// TODO: Log
|
||||
count_card.state = CandidateState::ELECTED;
|
||||
state.num_elected += 1;
|
||||
count_card.order_elected = state.num_elected as isize;
|
||||
state.logs.push(format!("{} meets the quota and is elected.", candidate.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,7 +200,12 @@ where
|
||||
// Transfer candidate votes
|
||||
// Unweighted inclusive Gregory
|
||||
// TODO: Other methods
|
||||
//let transfer_value = surplus.clone() / &result.total_ballots;
|
||||
let transfer_value = surplus.clone() / &result.total_ballots;
|
||||
|
||||
state.kind = Some("Surplus of");
|
||||
state.title = String::from(&elected_candidate.name);
|
||||
state.logs.push(format!("Surplus of {} distributed at value {:.2}.", elected_candidate.name, transfer_value));
|
||||
|
||||
let mut checksum = N::new();
|
||||
|
||||
for (candidate, entry) in result.candidates.into_iter() {
|
||||
@ -234,6 +250,9 @@ where
|
||||
|
||||
pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
|
||||
if state.election.candidates.len() - state.num_excluded <= state.election.seats {
|
||||
state.kind = None;
|
||||
state.title = "Bulk election".to_string();
|
||||
|
||||
// Bulk elect all remaining candidates
|
||||
let mut hopefuls: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
|
||||
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
|
||||
@ -242,10 +261,12 @@ pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
|
||||
// TODO: Handle ties
|
||||
hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
|
||||
|
||||
for (_, count_card) in hopefuls.into_iter() {
|
||||
for (candidate, count_card) in hopefuls.into_iter() {
|
||||
count_card.state = CandidateState::ELECTED;
|
||||
state.num_elected += 1;
|
||||
count_card.order_elected = state.num_elected as isize;
|
||||
|
||||
state.logs.push(format!("{} elected to fill remaining vacancies.", candidate.name));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -264,6 +285,11 @@ pub fn exclude_hopefuls<N: Number>(state: &mut CountState<N>) -> bool {
|
||||
|
||||
// Exclude lowest ranked candidate
|
||||
let excluded_candidate = hopefuls.first().unwrap().0;
|
||||
|
||||
state.kind = Some("Exclusion of");
|
||||
state.title = String::from(&excluded_candidate.name);
|
||||
state.logs.push(format!("No surpluses to distribute, so {} is excluded.", excluded_candidate.name));
|
||||
|
||||
exclude_candidate(state, excluded_candidate);
|
||||
|
||||
return true;
|
||||
@ -277,6 +303,11 @@ pub fn continue_exclusion<N: Number>(state: &mut CountState<N>) -> bool {
|
||||
if excluded_with_votes.len() > 0 {
|
||||
excluded_with_votes.sort_unstable_by(|a, b| a.1.order_elected.partial_cmp(&b.1.order_elected).unwrap());
|
||||
let excluded_candidate = excluded_with_votes.first().unwrap().0;
|
||||
|
||||
state.kind = Some("Exclusion of");
|
||||
state.title = String::from(&excluded_candidate.name);
|
||||
state.logs.push(format!("Continuing exclusion of {}.", excluded_candidate.name));
|
||||
|
||||
exclude_candidate(state, excluded_candidate);
|
||||
return true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user