OpenTally/src/election.rs

225 lines
5.1 KiB
Rust
Raw Normal View History

2021-05-28 19:58:40 +10:00
/* 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 <https://www.gnu.org/licenses/>.
*/
use crate::logger::Logger;
2021-05-28 19:58:40 +10:00
use crate::numbers::Number;
use std::collections::HashMap;
pub struct Election<N> {
pub seats: usize,
pub candidates: Vec<Candidate>,
pub ballots: Vec<Ballot<N>>,
}
impl<N: Number> Election<N> {
2021-05-30 18:28:39 +10:00
pub fn from_blt<I: Iterator<Item=String>>(mut lines: I) -> Self {
2021-05-28 19:58:40 +10:00
// Read first line
2021-05-30 18:28:39 +10:00
let line = lines.next().expect("Unexpected EOF");
2021-05-28 19:58:40 +10:00
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 {
seats: seats,
candidates: Vec::with_capacity(num_candidates),
ballots: Vec::new(),
};
// Read ballots
for line in &mut lines {
if line == "0" {
break;
}
let mut bits = line.split(" ");
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::<usize>().expect("Syntax Error");
ballot.preferences.push(preference - 1);
}
}
election.ballots.push(ballot);
}
// Read candidates
for line in lines.take(num_candidates) {
2021-05-30 18:28:39 +10:00
let mut line = &line[..];
2021-05-28 19:58:40 +10:00
if line.starts_with("\"") && line.ends_with("\"") {
line = &line[1..line.len()-1];
}
election.candidates.push(Candidate { name: line.to_string() });
}
return election;
}
}
#[derive(PartialEq, Eq, Hash)]
pub struct Candidate {
pub name: String,
}
#[derive(Clone)]
pub struct CountState<'a, N> {
pub election: &'a Election<N>,
pub candidates: HashMap<&'a Candidate, CountCard<'a, N>>,
pub exhausted: CountCard<'a, N>,
pub loss_fraction: CountCard<'a, N>,
2021-05-28 19:58:40 +10:00
pub quota: N,
pub num_elected: usize,
pub num_excluded: usize,
2021-05-29 01:22:46 +10:00
pub kind: Option<&'a str>,
pub title: String,
pub logger: Logger<'a>,
2021-05-28 19:58:40 +10:00
}
impl<'a, N: Number> CountState<'a, N> {
pub fn new(election: &'a Election<N>) -> Self {
let mut state = CountState {
election: &election,
candidates: HashMap::new(),
exhausted: CountCard::new(),
loss_fraction: CountCard::new(),
2021-05-28 19:58:40 +10:00
quota: N::new(),
num_elected: 0,
num_excluded: 0,
2021-05-29 01:22:46 +10:00
kind: None,
title: String::new(),
logger: Logger { entries: Vec::new() },
2021-05-28 19:58:40 +10:00
};
for candidate in election.candidates.iter() {
state.candidates.insert(candidate, CountCard::new());
}
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();
2021-05-28 19:58:40 +10:00
}
}
2021-05-29 01:22:46 +10:00
#[allow(dead_code)]
2021-05-28 19:58:40 +10:00
pub enum CountStateOrRef<'a, N> {
2021-05-29 01:22:46 +10:00
State(CountState<'a, N>), // NYI: May be used e.g. for tie-breaking or rollback-based constraints
2021-05-28 19:58:40 +10:00
Ref(&'a CountState<'a, N>),
}
impl<'a, N> CountStateOrRef<'a, N> {
2021-05-29 01:22:46 +10:00
pub fn from(state: &'a CountState<N>) -> Self {
return Self::Ref(state);
}
2021-05-28 19:58:40 +10:00
pub fn as_ref(&self) -> &CountState<N> {
match self {
CountStateOrRef::State(state) => &state,
CountStateOrRef::Ref(state) => state,
}
}
}
pub struct StageResult<'a, N> {
2021-05-29 01:22:46 +10:00
pub kind: Option<&'a str>,
pub title: &'a String,
pub logs: Vec<String>,
2021-05-28 19:58:40 +10:00
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<Parcel<'a, N>>,
}
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<Vote<'a, N>>;
#[derive(Clone)]
pub struct Vote<'a, N> {
pub ballot: &'a Ballot<N>,
pub value: N,
pub up_to_pref: usize,
}
pub struct Ballot<N> {
pub orig_value: N,
pub preferences: Vec<usize>,
}
#[allow(dead_code)]
#[derive(PartialEq)]
#[derive(Clone)]
pub enum CandidateState {
HOPEFUL,
GUARDED,
ELECTED,
DOOMED,
EXCLUDED,
}