stv_rs/types/
mod.rs

1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types to represent an election.
16
17mod ballot;
18mod util;
19
20pub use ballot::Ballot;
21use log::Level::{Debug, Trace};
22use log::{debug, log_enabled, trace};
23use std::borrow::Borrow;
24use std::collections::{BTreeMap, HashMap};
25use util::count_vec_allocations;
26
27/// Election input, representing a parsed ballot file.
28#[derive(Debug, PartialEq, Eq)]
29pub struct Election {
30    /// Name of the election.
31    pub title: String,
32    /// Number of candidates.
33    pub num_candidates: usize,
34    /// Number of elected seats.
35    pub num_seats: usize,
36    /// Number of ballots that were cast in the election.
37    pub num_ballots: usize,
38    /// Candidates in this election.
39    pub candidates: Vec<Candidate>,
40    /// Ballots that were cast in this election.
41    pub ballots: Vec<Ballot>,
42    /// Tie-break order of candidates, mapping each candidate ID to its order
43    /// in the tie break.
44    pub tie_order: HashMap<usize, usize>,
45}
46
47impl Election {
48    /// Returns a new builder.
49    pub fn builder() -> ElectionBuilder {
50        ElectionBuilder::default()
51    }
52
53    /// Returns true if any ballot contains candidates ranked equally.
54    pub fn has_any_tied_ballot(&self) -> bool {
55        self.ballots.iter().any(|b| b.has_tie())
56    }
57
58    pub(crate) fn debug_allocations(&self) {
59        if !log_enabled!(Debug) {
60            return;
61        }
62
63        let mut allocations = BTreeMap::new();
64        count_vec_allocations(&mut allocations, &self.ballots);
65        for b in &self.ballots {
66            b.count_allocations(&mut allocations);
67        }
68        let mut total_count = 0;
69        let mut total_size = 0;
70        for (size, count) in allocations.iter() {
71            total_count += count;
72            total_size += size * count;
73            debug!(
74                "Allocations of {size} bytes: {count} => {} bytes",
75                size * count
76            );
77        }
78        debug!("Ballots use {total_size} bytes in {total_count} allocations");
79        let ballots_len = self.ballots.len() as f64;
80        debug!(
81            "Each ballot uses {} bytes in {} allocations",
82            total_size as f64 / ballots_len,
83            total_count as f64 / ballots_len
84        );
85
86        if !log_enabled!(Trace) {
87            return;
88        }
89
90        trace!("Allocated addresses:");
91        let mut diffs = BTreeMap::new();
92        let mut prev = None;
93        for b in &self.ballots {
94            for address in b.allocated_addresses() {
95                match prev {
96                    None => trace!("- {address:#x?}"),
97                    Some(prev) => {
98                        let diff = if address >= prev {
99                            trace!("- {address:#x?} (+{:#x?})", address - prev);
100                            address - prev
101                        } else {
102                            trace!("- {address:#x?} (-{:#x?})", prev - address);
103                            prev - address
104                        };
105                        *diffs.entry(diff.checked_ilog2().unwrap_or(0)).or_insert(0) += 1;
106                    }
107                }
108                prev = Some(address);
109            }
110        }
111
112        trace!("Histogram of sequential jumps between allocated addresses:");
113        let max_count = diffs.values().max().unwrap();
114        for (&diff_log2, count) in diffs.iter() {
115            let start = if diff_log2 == 0 { 0 } else { 1u64 << diff_log2 };
116            let end = (1u64 << (diff_log2 + 1)) - 1;
117
118            let count_stars = count * 40 / max_count;
119            let count_spaces = 40 - count_stars;
120            let stars = &"****************************************"[..count_stars];
121            let spaces = &"                                        "[..count_spaces];
122            trace!("{stars}{spaces} {start}..={end}: {count}");
123        }
124    }
125}
126
127/// Builder for the [`Election`] type.
128#[derive(Default)]
129pub struct ElectionBuilder {
130    title: Option<String>,
131    num_seats: Option<usize>,
132    num_ballots: Option<usize>,
133    candidates: Vec<Candidate>,
134    ballots: Vec<Ballot>,
135    tie_order: Option<HashMap<usize, usize>>,
136}
137
138impl ElectionBuilder {
139    /// Build the [`Election`] object.
140    pub fn build(self) -> Election {
141        let num_ballots = self
142            .num_ballots
143            .unwrap_or_else(|| self.ballots.iter().map(|b| b.count()).sum());
144        let num_candidates = self.candidates.len();
145        Election {
146            title: self.title.unwrap(),
147            num_candidates,
148            num_seats: self.num_seats.unwrap(),
149            num_ballots,
150            candidates: self.candidates,
151            ballots: self.ballots,
152            tie_order: self
153                .tie_order
154                .unwrap_or_else(|| (0..num_candidates).map(|i| (i, i)).collect()),
155        }
156    }
157
158    /// Sets the name of the election.
159    pub fn title(mut self, title: &str) -> Self {
160        self.title = Some(title.to_owned());
161        self
162    }
163
164    /// Sets the number of elected seats.
165    pub fn num_seats(mut self, num_seats: usize) -> Self {
166        self.num_seats = Some(num_seats);
167        self
168    }
169
170    /// Sets the number of ballots cast in the election.
171    pub fn num_ballots(mut self, num_ballots: usize) -> Self {
172        self.num_ballots = Some(num_ballots);
173        self
174    }
175
176    /// Checks that the given number of ballots is consistent with the actual
177    /// number of ballots previously set with [`Self::ballots()`].
178    pub fn check_num_ballots(mut self, num_ballots: usize) -> Self {
179        assert_eq!(num_ballots, self.ballots.iter().map(|b| b.count()).sum());
180        self.num_ballots = Some(num_ballots);
181        self
182    }
183
184    /// Sets the list of candidates in the election.
185    pub fn candidates(mut self, candidates: impl Into<Vec<Candidate>>) -> Self {
186        self.candidates = candidates.into();
187        self
188    }
189
190    /// Sets the list of ballots in the election.
191    pub fn ballots(mut self, ballots: impl Into<Vec<Ballot>>) -> Self {
192        self.ballots = ballots.into();
193        self
194    }
195
196    /// Sets the tie-break order of candidates in the election.
197    pub fn tie_order(mut self, order: impl Borrow<[usize]>) -> Self {
198        assert_eq!(order.borrow().len(), self.candidates.len());
199        let mut tie_order = HashMap::new();
200        for (i, &c) in order.borrow().iter().enumerate() {
201            assert!(c < self.candidates.len());
202            assert!(tie_order.insert(c, i).is_none());
203        }
204        self.tie_order = Some(tie_order);
205        self
206    }
207}
208
209/// Candidate in an election.
210#[derive(Debug, PartialEq, Eq)]
211pub struct Candidate {
212    /// Nickname, used for parsing ballots.
213    pub nickname: String,
214    /// Full name, used to output results.
215    pub name: String,
216    /// Whether the candidate has withdrawn from the election.
217    pub is_withdrawn: bool,
218}
219
220impl Candidate {
221    /// Constructs a new [`Candidate`].
222    pub fn new(nickname: impl Into<String>, is_withdrawn: bool) -> Self {
223        let nickname = nickname.into();
224        let mut name = nickname.clone().into_bytes();
225        if let Some(x) = name.first_mut() {
226            *x = x.to_ascii_uppercase();
227        }
228        let name = String::from_utf8(name).unwrap();
229        Candidate {
230            nickname,
231            name,
232            is_withdrawn,
233        }
234    }
235}
236
237/// An election result.
238#[derive(Debug, PartialEq, Eq)]
239pub struct ElectionResult {
240    /// List of elected candidates, by election order.
241    pub elected: Vec<usize>,
242    /// List of non-elected candidates, by non-election order.
243    pub not_elected: Vec<usize>,
244}