loader working
This commit is contained in:
parent
878c3e577e
commit
29ffe1190f
|
|
@ -6,99 +6,36 @@ version = 4
|
|||
name = "ballot-counter"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"itertools",
|
||||
"quick-csv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.1"
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
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 = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.12"
|
||||
name = "quick-csv"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d"
|
||||
checksum = "edf4b7701db7d2e4c9c010f21eebd8676a50f79223a0cf858162d24bff47338c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"rustc-serialize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[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"
|
||||
checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401"
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
csv = "1.3.1"
|
||||
itertools = "0.14.0"
|
||||
quick-csv = "0.1.6"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Candidate {
|
||||
pub name: String,
|
||||
pub party_id: Option<usize>,
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
40
src/main.rs
40
src/main.rs
|
|
@ -1,3 +1,41 @@
|
|||
use std::env;
|
||||
|
||||
use ballot::{Ballot, BallotType};
|
||||
use header::Header;
|
||||
|
||||
pub mod candidate;
|
||||
mod ballot;
|
||||
mod header;
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue