/* OpenTally: Open-source election vote counting * Copyright © 2021 Lee Yingtong Li (RunasSudo) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use crate::logger::Logger; use crate::numbers::Number; use std::collections::HashMap; pub struct Election { pub name: String, pub seats: usize, pub candidates: Vec, pub withdrawn_candidates: Vec, pub ballots: Vec>, } impl Election { pub fn from_blt>(mut lines: I) -> Self { // Read first line let line = lines.next().expect("Unexpected EOF"); let mut bits = line.split(" "); let num_candidates = bits.next().expect("Syntax Error").parse().expect("Syntax Error"); let seats: usize = bits.next().expect("Syntax Error").parse().expect("Syntax Error"); // Initialise the Election object let mut election = Election { name: String::new(), seats: seats, candidates: Vec::with_capacity(num_candidates), withdrawn_candidates: Vec::new(), ballots: Vec::new(), }; // Read ballots for line in &mut lines { if line == "0" { break; } let mut bits = line.split(" "); if line.starts_with("-") { // Withdrawn candidates for bit in bits.into_iter() { let val = bit[1..bit.len()].parse::().expect("Syntax Error"); election.withdrawn_candidates.push(val - 1); } continue; } let value = N::parse(bits.next().expect("Syntax Error")); let mut ballot = Ballot { orig_value: value, preferences: Vec::new(), }; for preference in bits { if preference != "0" { let preference = preference.parse::().expect("Syntax Error"); ballot.preferences.push(preference - 1); } } election.ballots.push(ballot); } // Read candidates for line in lines.by_ref().take(num_candidates) { let mut line = &line[..]; if line.starts_with("\"") && line.ends_with("\"") { line = &line[1..line.len()-1]; } election.candidates.push(Candidate { name: line.to_string() }); } // Read name let line = lines.next().expect("Syntax Error"); let mut line = &line[..]; if line.starts_with("\"") && line.ends_with("\"") { line = &line[1..line.len()-1]; } election.name.push_str(line); return election; } pub fn normalise_ballots(&mut self) { let mut normalised_ballots = Vec::new(); for ballot in self.ballots.iter() { let mut n = N::new(); let one = N::one(); while n < ballot.orig_value { let new_ballot = Ballot { orig_value: N::one(), preferences: ballot.preferences.clone(), }; normalised_ballots.push(new_ballot); n += &one; } } self.ballots = normalised_ballots; } } #[derive(PartialEq, Eq, Hash)] pub struct Candidate { pub name: String, } #[derive(Clone)] pub struct CountState<'a, N> { pub election: &'a Election, pub candidates: HashMap<&'a Candidate, CountCard<'a, N>>, pub exhausted: CountCard<'a, N>, pub loss_fraction: CountCard<'a, N>, pub forwards_tiebreak: Option>, pub backwards_tiebreak: Option>, pub quota: Option, pub vote_required_election: Option, pub num_elected: usize, pub num_excluded: usize, pub kind: Option<&'a str>, pub title: String, pub logger: Logger<'a>, } impl<'a, N: Number> CountState<'a, N> { pub fn new(election: &'a Election) -> Self { let mut state = CountState { election: &election, candidates: HashMap::new(), exhausted: CountCard::new(), loss_fraction: CountCard::new(), forwards_tiebreak: None, backwards_tiebreak: None, quota: None, vote_required_election: None, num_elected: 0, num_excluded: 0, kind: None, title: String::new(), logger: Logger { entries: Vec::new() }, }; for candidate in election.candidates.iter() { state.candidates.insert(candidate, CountCard::new()); } for withdrawn_idx in election.withdrawn_candidates.iter() { state.candidates.get_mut(&election.candidates[*withdrawn_idx]).unwrap().state = CandidateState::Withdrawn; } return state; } pub fn step_all(&mut self) { for (_, count_card) in self.candidates.iter_mut() { count_card.step(); } self.exhausted.step(); self.loss_fraction.step(); } } #[allow(dead_code)] pub enum CountStateOrRef<'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) -> Self { return Self::Ref(state); } pub fn as_ref(&self) -> &CountState { match self { CountStateOrRef::State(state) => &state, CountStateOrRef::Ref(state) => state, } } } pub struct StageResult<'a, N> { pub kind: Option<&'a str>, pub title: &'a String, pub logs: Vec, pub state: CountStateOrRef<'a, N>, } #[derive(Clone)] pub struct CountCard<'a, N> { pub state: CandidateState, pub order_elected: isize, pub orig_votes: N, pub transfers: N, pub votes: N, pub parcels: Vec>, } impl<'a, N: Number> CountCard<'a, N> { pub fn new() -> Self { return CountCard { state: CandidateState::Hopeful, order_elected: 0, orig_votes: N::new(), transfers: N::new(), votes: N::new(), parcels: Vec::new(), }; } //pub fn votes(&'a self) -> N { // return self.orig_votes.clone() + &self.transfers; //} pub fn transfer(&mut self, transfer: &'_ N) { self.transfers += transfer; self.votes += transfer; } pub fn step(&mut self) { self.orig_votes = self.votes.clone(); self.transfers = N::new(); } } pub type Parcel<'a, N> = Vec>; #[derive(Clone)] pub struct Vote<'a, N> { pub ballot: &'a Ballot, pub value: N, pub up_to_pref: usize, } pub struct Ballot { pub orig_value: N, pub preferences: Vec, } #[allow(dead_code)] #[derive(PartialEq)] #[derive(Clone)] pub enum CandidateState { Hopeful, Guarded, Elected, Doomed, Withdrawn, Excluded, }