loader working

This commit is contained in:
Jay Robson 2025-05-03 21:22:32 +10:00
parent 878c3e577e
commit 29ffe1190f
6 changed files with 188 additions and 84 deletions

101
Cargo.lock generated
View File

@ -6,99 +6,36 @@ version = 4
name = "ballot-counter" name = "ballot-counter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"csv", "itertools",
"quick-csv",
] ]
[[package]] [[package]]
name = "csv" name = "either"
version = "1.3.1" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [ dependencies = [
"csv-core", "either",
"itoa",
"ryu",
"serde",
] ]
[[package]] [[package]]
name = "csv-core" name = "quick-csv"
version = "0.1.12" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" checksum = "edf4b7701db7d2e4c9c010f21eebd8676a50f79223a0cf858162d24bff47338c"
dependencies = [ dependencies = [
"memchr", "rustc-serialize",
] ]
[[package]] [[package]]
name = "itoa" name = "rustc-serialize"
version = "1.0.15" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

View File

@ -4,4 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
csv = "1.3.1" itertools = "0.14.0"
quick-csv = "0.1.6"

75
src/ballot.rs Normal file
View File

@ -0,0 +1,75 @@
use itertools::Itertools;
use crate::header::Header;
#[derive(Copy,Clone,Debug,PartialEq)]
pub enum BallotType {
Party,
Candidate,
}
#[derive(Debug)]
pub struct Ballot {
pub state: String,
pub division: String,
pub collection_point: String,
pub collection_point_id: usize,
pub batch_id: usize,
pub paper_id: usize,
pub votes: Vec<usize>,
pub ty: BallotType,
}
impl Ballot {
pub fn parse_rows<T: std::io::BufRead>(csv: quick_csv::Csv<T>, header: &Header) -> Result<Vec<Ballot>, Box<dyn std::error::Error>> {
let mut ballots = Vec::<Self>::new();
let col_filter = |(index, place): (usize, &str)| match place.parse::<usize>() {
Ok(v) => Some((index, v - 1)),
Err(_) => None,
};
for result in csv {
let row = result?;
let mut cols = row.columns()?;
let (state, division, collection_point, collection_point_id, batch_id, paper_id) = cols.next_tuple().ok_or("Missing columns")?;
let cols = cols.collect_vec();
let mut votes: Vec<Option<usize>>;
let ty: BallotType;
// below the line votes
if cols.len() > header.parties.len() {
ty = BallotType::Candidate;
votes = vec![None; header.candidates.len()];
for (col_id, place) in cols.iter().copied().skip(header.parties.len()).take(header.candidates.len()).enumerate().filter_map(col_filter) {
votes[place] = Some(col_id);
}
}
// above the line votes
else {
ty = BallotType::Party;
votes = vec![None; header.parties.len()];
for (col_id, place) in cols.iter().copied().take(header.parties.len()).enumerate().filter_map(col_filter) {
votes[place] = Some(col_id);
}
}
ballots.push(Ballot {
state: state.to_owned(),
division: division.to_owned(),
collection_point: collection_point.to_owned(),
collection_point_id: collection_point_id.parse()?,
batch_id: batch_id.parse()?,
paper_id: paper_id.parse()?,
votes: votes.iter().copied().filter_map(|v| v).collect(),
ty,
});
}
Ok(ballots)
}
}

7
src/candidate.rs Normal file
View File

@ -0,0 +1,7 @@
#[derive(Debug)]
pub struct Candidate {
pub name: String,
pub party_id: Option<usize>,
}

46
src/header.rs Normal file
View File

@ -0,0 +1,46 @@
use std::collections::HashMap;
use itertools::Itertools;
use crate::candidate::Candidate;
#[derive(Debug)]
pub struct Header {
pub parties: Vec<String>,
pub candidates: Vec<Candidate>,
}
impl Header {
pub fn parse(row: quick_csv::Row) -> Result<Header, Box<dyn std::error::Error>> {
let mut header = Header {
parties: Vec::new(),
candidates: Vec::new(),
};
let mut parties_lookup = HashMap::<&str, usize>::new();
for col in row.columns()?.skip(6) {
let [party, name] = col.split(':').next_array().ok_or("Missing ':'")?;
if party == "UG" { // independents
header.candidates.push(Candidate {
name: name.to_owned(),
party_id: None,
});
}
else if let Some(party_id) = parties_lookup.get(party).copied() {
header.candidates.push(Candidate {
name: name.to_owned(),
party_id: Some(party_id),
});
}
else {
parties_lookup.insert(party, header.parties.len());
header.parties.push(name.to_owned());
}
}
Ok(header)
}
}

View File

@ -1,3 +1,41 @@
use std::env;
use ballot::{Ballot, BallotType};
use header::Header;
pub mod candidate;
mod ballot;
mod header;
fn main() { fn main() {
println!("Hello, world!"); let csv_path = {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <filename.csv>", args[0]);
return;
} }
args[1].clone()
};
let mut csv = quick_csv::Csv::from_file(csv_path).unwrap().flexible(true);
let header = Header::parse(csv.next().unwrap().unwrap()).unwrap();
let ballots = Ballot::parse_rows(csv, &header).unwrap();
println!("Parties: {}", header.parties.len());
println!("Candidates: {}", header.candidates.len());
let total = ballots.len() as u32;
let mut total_atl = 0u32;
let mut total_btl = 0u32;
for ballot in ballots.iter() {
*match ballot.ty {
BallotType::Party => &mut total_atl,
BallotType::Candidate => &mut total_btl,
} += 1;
}
println!("Above the line: {total_atl} {}%", f64::from(total_atl) / f64::from(total) * 100.0);
println!("Below the line: {total_btl} {}%", f64::from(total_btl) / f64::from(total) * 100.0);
println!("Total: {total}");
}