115 lines
3.5 KiB
Rust
115 lines
3.5 KiB
Rust
use std::env;
|
|
|
|
use ballot::BallotType;
|
|
use counter::Counter;
|
|
use itertools::Itertools;
|
|
|
|
pub mod candidate;
|
|
pub mod ballot;
|
|
pub mod header;
|
|
pub mod party;
|
|
mod counter;
|
|
|
|
fn main() {
|
|
let (csv_path, winner_count) = {
|
|
let args: Vec<String> = env::args().collect();
|
|
if args.len() != 3 {
|
|
eprintln!("Usage: {} <filename.csv> <winners>", args[0]);
|
|
return;
|
|
}
|
|
(args[1].clone(), args[2].parse::<usize>().unwrap())
|
|
};
|
|
let csv = quick_csv::Csv::from_file(csv_path).unwrap().flexible(true);
|
|
let counter = Counter::new(csv).unwrap();
|
|
|
|
eprintln!("Header: {:#?}", counter.header);
|
|
eprintln!("Parties: {}", counter.header.parties.len());
|
|
eprintln!("Candidates: {}", counter.header.candidates.len());
|
|
|
|
let total = counter.ballots.len() as u32;
|
|
let mut total_atl = 0u32;
|
|
let mut total_btl = 0u32;
|
|
|
|
for ballot in counter.ballots.iter() {
|
|
*match ballot.ty {
|
|
BallotType::Party => &mut total_atl,
|
|
BallotType::Candidate => &mut total_btl,
|
|
} += 1;
|
|
}
|
|
|
|
eprintln!("Above the line: {total_atl} {}%", f64::from(total_atl) / f64::from(total) * 100.0);
|
|
eprintln!("Below the line: {total_btl} {}%", f64::from(total_btl) / f64::from(total) * 100.0);
|
|
eprintln!("Total: {total}");
|
|
|
|
let ty = BallotType::Candidate;
|
|
let names = match ty {
|
|
BallotType::Party => counter.header.parties.iter().map(|v| v.name.clone()).collect_vec(),
|
|
BallotType::Candidate => counter.header.candidates.iter().map(|v| match v.party_id {
|
|
Some(party_id) => format!("{}: {}", v.name, &counter.header.parties[party_id].name),
|
|
None => v.name.to_owned(),
|
|
}).collect_vec(),
|
|
};
|
|
let mut running = vec![true; names.len()];
|
|
|
|
print!(",,,");
|
|
for name in names.iter() {
|
|
print!(",{name:?},");
|
|
}
|
|
println!();
|
|
eprintln!("Running election rounds:");
|
|
|
|
let to_percent = |score: f64| score / f64::from(total) * 100.0;
|
|
let mut scores_last: Option<Vec<f64>> = None;
|
|
let mut id_worst_last: Option<usize> = None;
|
|
|
|
for index in 0..(running.len() - winner_count + 1) {
|
|
let scores = counter.count_all_with_powers_of_2(ty, &running);
|
|
let scores_ordered = scores.iter().copied().enumerate().filter(|&(i,_)| running[i]).sorted_by(|a, b| f64::total_cmp(&b.1, &a.1)).collect_vec();
|
|
let (id_worst, score_worst) = match scores_ordered.last() {
|
|
Some(&v) => v,
|
|
None => break,
|
|
};
|
|
|
|
if let Some(scores_last) = scores_last {
|
|
let id_worst_last = id_worst_last.unwrap();
|
|
let score_worst_last = scores_last[id_worst_last];
|
|
print!(",{:?},{score_worst_last},{:.3}%", names[id_worst_last], to_percent(score_worst_last));
|
|
for ((&score, &score_last), &running) in scores.iter().zip(scores_last.iter()).zip(running.iter()) {
|
|
if running {
|
|
let score_diff = score - score_last;
|
|
print!(",+{score_diff},+{:.3}%", score_diff / score_worst_last * 100.0);
|
|
} else {
|
|
print!(",,");
|
|
}
|
|
}
|
|
println!();
|
|
}
|
|
print!("{},,,", index + 1);
|
|
for (&score, &running) in scores.iter().zip(running.iter()) {
|
|
if running {
|
|
print!(",{score},{:.3}%", to_percent(score));
|
|
} else {
|
|
print!(",,");
|
|
}
|
|
}
|
|
println!();
|
|
running[id_worst] = false;
|
|
scores_last = Some(scores);
|
|
id_worst_last = Some(id_worst);
|
|
|
|
let mut total_votes = 0.0;
|
|
|
|
println!();
|
|
eprintln!("Round {}:", index+1);
|
|
for (place, &(id, score)) in scores_ordered.iter().take(winner_count).enumerate() {
|
|
eprintln!(" {}. {}: {score}, {:.3}%", place + 1, names[id], to_percent(score));
|
|
total_votes += score;
|
|
}
|
|
if scores_ordered.len() > winner_count {
|
|
eprintln!(" {}. {}: {score_worst}, {:.3}%", scores_ordered.len(), names[id_worst], to_percent(score_worst));
|
|
}
|
|
eprintln!(" Total: {total_votes}, {:.3}%", to_percent(total_votes.into()));
|
|
}
|
|
}
|
|
|