1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * core types
 */

use num::rational::BigRational;
use std::collections::HashSet;

// represents a candidate's index on the ballot paper
// ranges from 0..N-1 where N is the number of candidates
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct CandidateIndex(pub u8);

// represents a group's index on the ballot paper
// ranges from 0..N-1 where N is the number of groups
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct GroupIndex(pub u8);

// one ore more ballots entered into the count, all with the
// same form, and the current state of those ballots within
// the count (e.g. the current preference)
#[derive(Debug)]
pub struct BallotState {
    pub form: Vec<CandidateIndex>,
    pub count: u32,
    pub active_preference: usize,
}

pub struct CountResults {
    elected: Vec<CandidateIndex>,
    excluded: Vec<CandidateIndex>,
    inactive: HashSet<CandidateIndex>, // either elected or excluded; fast lookup
}

impl CountResults {
    pub fn new() -> CountResults {
        CountResults {
            elected: Vec::new(),
            excluded: Vec::new(),
            inactive: HashSet::new(),
        }
    }

    pub fn number_elected(&self) -> u32 {
        self.elected.len() as u32
    }

    pub fn get_elected(&self) -> &Vec<CandidateIndex> {
        &self.elected
    }

    pub fn get_excluded(&self) -> &Vec<CandidateIndex> {
        &self.excluded
    }

    pub fn candidate_elected(&mut self, candidate: CandidateIndex) {
        self.elected.push(candidate);
        self.inactive.insert(candidate);
    }

    pub fn candidate_excluded(&mut self, candidate: CandidateIndex) {
        self.excluded.push(candidate);
        self.inactive.insert(candidate);
    }

    pub fn candidate_is_inactive(&self, candidate: &CandidateIndex) -> bool {
        self.inactive.contains(&candidate)
    }
}

impl BallotState {
    pub fn alive(&self) -> bool {
        self.active_preference < self.form.len()
    }

    pub fn current_preference(&self) -> Option<CandidateIndex> {
        if self.alive() {
            Some(self.form[self.active_preference])
        } else {
            None
        }
    }

    pub fn to_next_preference(&mut self, results: &CountResults) {
        loop {
            self.active_preference += 1;
            match self.current_preference() {
                Some(candidate) => {
                    if !results.candidate_is_inactive(&candidate) {
                        break;
                    }
                }
                None => {
                    break;
                }
            }
        }
    }
}

// a collection of ballot states, all of which were transferred to
// the total of a candidate during a count. the member `votes`
// represents the integer value of the votes transferred to the
// candidate, after the application of the transfer value to the
// total number of papers in the transaction
pub struct BundleTransaction {
    pub ballot_states: Vec<BallotState>,
    pub transfer_value: BigRational,
    pub votes: u32,
    pub papers: u32,
}

pub struct CandidateData {
    pub count: usize,
    pub names: Vec<String>,
    pub parties: Vec<String>,
    pub tickets: Vec<Vec<CandidateIndex>>,
}

impl CandidateData {
    pub fn vec_names(&self, candidates: &Vec<CandidateIndex>) -> String {
        let names: Vec<String> = candidates.iter().map(|c| self.get_name(*c)).collect();
        names.join("; ")
    }
}

impl CandidateData {
    pub fn get_name(&self, idx: CandidateIndex) -> String {
        return self.names[idx.0 as usize].clone();
    }
    pub fn get_party(&self, idx: CandidateIndex) -> String {
        return self.parties[idx.0 as usize].clone();
    }
}