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_elected: usize,
|
||||||
pub num_excluded: usize,
|
pub num_excluded: usize,
|
||||||
|
|
||||||
|
pub kind: Option<&'a str>,
|
||||||
|
pub title: String,
|
||||||
|
pub logs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, N: Number> CountState<'a, N> {
|
impl<'a, N: Number> CountState<'a, N> {
|
||||||
|
@ -109,6 +113,9 @@ impl<'a, N: Number> CountState<'a, N> {
|
||||||
quota: N::new(),
|
quota: N::new(),
|
||||||
num_elected: 0,
|
num_elected: 0,
|
||||||
num_excluded: 0,
|
num_excluded: 0,
|
||||||
|
kind: None,
|
||||||
|
title: String::new(),
|
||||||
|
logs: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for candidate in election.candidates.iter() {
|
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> {
|
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>),
|
Ref(&'a CountState<'a, N>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, N> CountStateOrRef<'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> {
|
pub fn as_ref(&self) -> &CountState<N> {
|
||||||
match self {
|
match self {
|
||||||
CountStateOrRef::State(state) => &state,
|
CountStateOrRef::State(state) => &state,
|
||||||
|
@ -142,8 +154,9 @@ impl<'a, N> CountStateOrRef<'a, N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StageResult<'a, N> {
|
pub struct StageResult<'a, N> {
|
||||||
pub title: &'a str,
|
pub kind: Option<&'a str>,
|
||||||
pub logs: Vec<&'a str>,
|
pub title: &'a String,
|
||||||
|
pub logs: &'a Vec<String>,
|
||||||
pub state: CountStateOrRef<'a, N>,
|
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) {
|
fn print_stage<N: Number>(stage_num: usize, result: &StageResult<N>, cmd_opts: &STV) {
|
||||||
// Print stage details
|
// 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(" "));
|
println!("{}", result.logs.join(" "));
|
||||||
|
|
||||||
let state = result.state.as_ref();
|
let state = result.state.as_ref();
|
||||||
|
@ -106,9 +109,17 @@ 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.logs,
|
||||||
|
state: CountStateOrRef::from(&state),
|
||||||
|
};
|
||||||
|
print_stage(stage_num, &result, &cmd_opts);
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let should_clone_state = false;
|
|
||||||
|
|
||||||
// Read arguments
|
// Read arguments
|
||||||
let opts: Opts = Opts::parse();
|
let opts: Opts = Opts::parse();
|
||||||
let Command::STV(cmd_opts) = opts.command;
|
let Command::STV(cmd_opts) = opts.command;
|
||||||
|
@ -127,17 +138,11 @@ fn main() {
|
||||||
stv::elect_meeting_quota(&mut state);
|
stv::elect_meeting_quota(&mut state);
|
||||||
|
|
||||||
// Display
|
// 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;
|
let mut stage_num = 1;
|
||||||
|
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
state.logs.clear();
|
||||||
state.step_all();
|
state.step_all();
|
||||||
stage_num += 1;
|
stage_num += 1;
|
||||||
|
|
||||||
|
@ -149,48 +154,28 @@ fn main() {
|
||||||
// Continue exclusions
|
// Continue exclusions
|
||||||
if stv::continue_exclusion(&mut state) {
|
if stv::continue_exclusion(&mut state) {
|
||||||
stv::elect_meeting_quota(&mut state);
|
stv::elect_meeting_quota(&mut state);
|
||||||
let result = StageResult {
|
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distribute surpluses
|
// Distribute surpluses
|
||||||
if stv::distribute_surpluses(&mut state) {
|
if stv::distribute_surpluses(&mut state) {
|
||||||
stv::elect_meeting_quota(&mut state);
|
stv::elect_meeting_quota(&mut state);
|
||||||
let result = StageResult {
|
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt bulk election
|
// Attempt bulk election
|
||||||
if stv::bulk_elect(&mut state) {
|
if stv::bulk_elect(&mut state) {
|
||||||
stv::elect_meeting_quota(&mut state);
|
stv::elect_meeting_quota(&mut state);
|
||||||
let result = StageResult {
|
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exclude lowest hopeful
|
// Exclude lowest hopeful
|
||||||
if stv::exclude_hopefuls(&mut state) {
|
if stv::exclude_hopefuls(&mut state) {
|
||||||
stv::elect_meeting_quota(&mut state);
|
stv::elect_meeting_quota(&mut state);
|
||||||
let result = StageResult {
|
make_and_print_result(stage_num, &state, &cmd_opts);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,11 +109,18 @@ pub fn distribute_first_preferences<N: Number>(state: &mut CountState<N>) {
|
||||||
let parcel = result.exhausted.votes as Parcel<N>;
|
let parcel = result.exhausted.votes as Parcel<N>;
|
||||||
state.exhausted.parcels.push(parcel);
|
state.exhausted.parcels.push(parcel);
|
||||||
state.exhausted.transfer(&result.exhausted.num_votes);
|
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>) {
|
pub fn calculate_quota<N: Number>(state: &mut CountState<N>) {
|
||||||
|
let mut log = String::new();
|
||||||
|
|
||||||
// Calculate the total vote
|
// Calculate the total vote
|
||||||
state.quota = state.candidates.values().fold(N::zero(), |acc, cc| { acc + &cc.votes });
|
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
|
// TODO: Different quotas
|
||||||
state.quota /= N::from(state.election.seats + 1);
|
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
|
// TODO: Different rounding rules
|
||||||
state.quota += N::one();
|
state.quota += N::one();
|
||||||
state.quota.floor_mut();
|
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 {
|
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());
|
cands_meeting_quota.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
|
||||||
|
|
||||||
// Declare elected in descending order of votes
|
// 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
|
// TODO: Log
|
||||||
count_card.state = CandidateState::ELECTED;
|
count_card.state = CandidateState::ELECTED;
|
||||||
state.num_elected += 1;
|
state.num_elected += 1;
|
||||||
count_card.order_elected = state.num_elected as isize;
|
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
|
// Transfer candidate votes
|
||||||
// Unweighted inclusive Gregory
|
// Unweighted inclusive Gregory
|
||||||
// TODO: Other methods
|
// 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();
|
let mut checksum = N::new();
|
||||||
|
|
||||||
for (candidate, entry) in result.candidates.into_iter() {
|
for (candidate, entry) in result.candidates.into_iter() {
|
||||||
|
@ -234,6 +250,9 @@ where
|
||||||
|
|
||||||
pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
|
pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
|
||||||
if state.election.candidates.len() - state.num_excluded <= state.election.seats {
|
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
|
// Bulk elect all remaining candidates
|
||||||
let mut hopefuls: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
|
let mut hopefuls: Vec<(&&Candidate, &mut CountCard<N>)> = state.candidates.iter_mut()
|
||||||
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
|
.filter(|(_, cc)| cc.state == CandidateState::HOPEFUL)
|
||||||
|
@ -242,10 +261,12 @@ pub fn bulk_elect<N: Number>(state: &mut CountState<N>) -> bool {
|
||||||
// TODO: Handle ties
|
// TODO: Handle ties
|
||||||
hopefuls.sort_unstable_by(|a, b| a.1.votes.partial_cmp(&b.1.votes).unwrap());
|
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;
|
count_card.state = CandidateState::ELECTED;
|
||||||
state.num_elected += 1;
|
state.num_elected += 1;
|
||||||
count_card.order_elected = state.num_elected as isize;
|
count_card.order_elected = state.num_elected as isize;
|
||||||
|
|
||||||
|
state.logs.push(format!("{} elected to fill remaining vacancies.", candidate.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -264,6 +285,11 @@ pub fn exclude_hopefuls<N: Number>(state: &mut CountState<N>) -> bool {
|
||||||
|
|
||||||
// Exclude lowest ranked candidate
|
// Exclude lowest ranked candidate
|
||||||
let excluded_candidate = hopefuls.first().unwrap().0;
|
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);
|
exclude_candidate(state, excluded_candidate);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -277,6 +303,11 @@ pub fn continue_exclusion<N: Number>(state: &mut CountState<N>) -> bool {
|
||||||
if excluded_with_votes.len() > 0 {
|
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());
|
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;
|
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);
|
exclude_candidate(state, excluded_candidate);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue