1use crate::types::{Candidate, Election};
18use std::io::{self, Write};
19
20#[derive(Clone, Copy, PartialEq, Eq)]
22pub enum WriteTieOrder {
23 Always,
25 Never,
27 OnlyNonTrivial,
29}
30
31#[derive(Clone, Copy, PartialEq, Eq)]
33pub enum CandidateFormat {
34 Nicknames,
36 Numeric,
38}
39
40pub fn write_blt(
42 output: &mut impl Write,
43 election: &Election,
44 write_tie_order: WriteTieOrder,
45 candidate_format: CandidateFormat,
46) -> io::Result<()> {
47 let has_non_trivial_tie_order = election
48 .tie_order
49 .iter()
50 .any(|(candidate, order)| candidate != order);
51 if write_tie_order == WriteTieOrder::Never && has_non_trivial_tie_order {
52 panic!("Writing the tie order is disabled, but the tie order is non-trivial");
53 }
54
55 writeln!(output, "{} {}", election.num_candidates, election.num_seats)?;
56
57 if candidate_format == CandidateFormat::Nicknames {
58 write!(output, "[nick")?;
59 for candidate in &election.candidates {
60 write!(output, " {}", candidate.nickname)?;
61 }
62 writeln!(output, "]")?;
63 }
64
65 if election
66 .candidates
67 .iter()
68 .any(|candidate| candidate.is_withdrawn)
69 {
70 write!(output, "[withdrawn")?;
71 for (i, candidate) in election
72 .candidates
73 .iter()
74 .enumerate()
75 .filter(|(_, candidate)| candidate.is_withdrawn)
76 {
77 write!(output, " ")?;
78 write_candidate(output, i, candidate, candidate_format)?;
79 }
80 writeln!(output, "]")?;
81 }
82
83 let write_tie_order = match write_tie_order {
84 WriteTieOrder::Always => true,
85 WriteTieOrder::Never => false,
86 WriteTieOrder::OnlyNonTrivial => has_non_trivial_tie_order,
87 };
88 if write_tie_order {
89 let mut tie_order = vec![!0; election.num_candidates];
90 for (&candidate, &rank) in &election.tie_order {
91 tie_order[rank] = candidate;
92 }
93
94 write!(output, "[tie")?;
95 for candidate in tie_order {
96 assert_ne!(candidate, !0);
97 write!(output, " ")?;
98 write_candidate(
99 output,
100 candidate,
101 &election.candidates[candidate],
102 candidate_format,
103 )?;
104 }
105 writeln!(output, "]")?;
106 }
107
108 for ballot in &election.ballots {
109 write!(output, "{}", ballot.count())?;
110 for ranking in ballot.order() {
111 write!(output, " ")?;
112 for (i, candidate) in ranking.iter().map(|&x| x.into()).enumerate() {
113 if i != 0 {
114 write!(output, "=")?;
115 }
116 write_candidate(
117 output,
118 candidate,
119 &election.candidates[candidate],
120 candidate_format,
121 )?;
122 }
123 }
124 writeln!(output, " 0")?;
125 }
126
127 writeln!(output, "0")?;
128 for candidate in &election.candidates {
129 writeln!(output, "\"{}\"", candidate.name)?;
130 }
131 writeln!(output, "\"{}\"", election.title)?;
132
133 Ok(())
134}
135
136fn write_candidate(
137 output: &mut impl Write,
138 index: usize,
139 candidate: &Candidate,
140 candidate_format: CandidateFormat,
141) -> io::Result<()> {
142 match candidate_format {
143 CandidateFormat::Nicknames => write!(output, "{}", candidate.nickname),
144 CandidateFormat::Numeric => write!(output, "{}", index + 1),
145 }
146}
147
148#[cfg(test)]
149mod test {
150 use super::*;
151 use crate::types::{Ballot, ElectionBuilder};
152
153 fn test_election_builder() -> ElectionBuilder {
154 Election::builder()
155 .title("Vegetable contest")
156 .num_seats(2)
157 .candidates([
158 Candidate::new("apple", false),
159 Candidate::new("banana", false),
160 Candidate::new("cherry", false),
161 Candidate::new("date", false),
162 ])
163 .ballots([
164 Ballot::new(1, [vec![0]]),
165 Ballot::new(2, [vec![2, 1], vec![3]]),
166 Ballot::new(3, [vec![1, 2]]),
167 Ballot::new(4, [vec![3, 1], vec![0, 2]]),
168 Ballot::new(5, [vec![1, 2, 3, 0]]),
169 ])
170 }
171
172 #[test]
173 fn test_write_blt_withdrawn() {
174 let election = Election::builder()
175 .title("Vegetable contest")
176 .num_seats(2)
177 .candidates([
178 Candidate::new("apple", true),
179 Candidate::new("banana", false),
180 Candidate::new("cherry", true),
181 Candidate::new("date", false),
182 ])
183 .ballots([
184 Ballot::new(1, [vec![0]]),
185 Ballot::new(2, [vec![2, 1], vec![3]]),
186 ])
187 .build();
188
189 let mut buf = Vec::new();
190 write_blt(
191 &mut buf,
192 &election,
193 WriteTieOrder::Always,
194 CandidateFormat::Nicknames,
195 )
196 .unwrap();
197
198 assert_eq!(
199 std::str::from_utf8(&buf).unwrap(),
200 r#"4 2
201[nick apple banana cherry date]
202[withdrawn apple cherry]
203[tie apple banana cherry date]
2041 apple 0
2052 cherry=banana date 0
2060
207"Apple"
208"Banana"
209"Cherry"
210"Date"
211"Vegetable contest"
212"#
213 );
214 }
215
216 #[test]
217 fn test_write_blt_with_ties_trivial_ties() {
218 let mut buf = Vec::new();
219 write_blt(
220 &mut buf,
221 &test_election_builder().build(),
222 WriteTieOrder::Always,
223 CandidateFormat::Nicknames,
224 )
225 .unwrap();
226
227 assert_eq!(
228 std::str::from_utf8(&buf).unwrap(),
229 r#"4 2
230[nick apple banana cherry date]
231[tie apple banana cherry date]
2321 apple 0
2332 cherry=banana date 0
2343 banana=cherry 0
2354 date=banana apple=cherry 0
2365 banana=cherry=date=apple 0
2370
238"Apple"
239"Banana"
240"Cherry"
241"Date"
242"Vegetable contest"
243"#
244 );
245 }
246
247 #[test]
248 fn test_write_blt_with_ties_has_ties() {
249 let mut buf = Vec::new();
250 write_blt(
251 &mut buf,
252 &test_election_builder().tie_order([1, 3, 2, 0]).build(),
253 WriteTieOrder::Always,
254 CandidateFormat::Nicknames,
255 )
256 .unwrap();
257
258 assert_eq!(
259 std::str::from_utf8(&buf).unwrap(),
260 r#"4 2
261[nick apple banana cherry date]
262[tie banana date cherry apple]
2631 apple 0
2642 cherry=banana date 0
2653 banana=cherry 0
2664 date=banana apple=cherry 0
2675 banana=cherry=date=apple 0
2680
269"Apple"
270"Banana"
271"Cherry"
272"Date"
273"Vegetable contest"
274"#
275 );
276 }
277
278 #[test]
279 fn test_write_blt_no_ties_trivial_ties() {
280 let mut buf = Vec::new();
281 write_blt(
282 &mut buf,
283 &test_election_builder().build(),
284 WriteTieOrder::Never,
285 CandidateFormat::Nicknames,
286 )
287 .unwrap();
288
289 assert_eq!(
290 std::str::from_utf8(&buf).unwrap(),
291 r#"4 2
292[nick apple banana cherry date]
2931 apple 0
2942 cherry=banana date 0
2953 banana=cherry 0
2964 date=banana apple=cherry 0
2975 banana=cherry=date=apple 0
2980
299"Apple"
300"Banana"
301"Cherry"
302"Date"
303"Vegetable contest"
304"#
305 );
306 }
307
308 #[test]
309 #[should_panic(
310 expected = "Writing the tie order is disabled, but the tie order is non-trivial"
311 )]
312 fn test_write_blt_no_ties_has_ties() {
313 let mut buf = Vec::new();
314 write_blt(
315 &mut buf,
316 &test_election_builder().tie_order([1, 3, 2, 0]).build(),
317 WriteTieOrder::Never,
318 CandidateFormat::Nicknames,
319 )
320 .unwrap();
321
322 assert_eq!(
323 std::str::from_utf8(&buf).unwrap(),
324 r#"4 2
325[nick apple banana cherry date]
326[tie banana date cherry apple]
3271 apple 0
3282 cherry=banana date 0
3293 banana=cherry 0
3304 date=banana apple=cherry 0
3315 banana=cherry=date=apple 0
3320
333"Apple"
334"Banana"
335"Cherry"
336"Date"
337"Vegetable contest"
338"#
339 );
340 }
341
342 #[test]
343 fn test_write_blt_maybe_ties_trivial_ties() {
344 let mut buf = Vec::new();
345 write_blt(
346 &mut buf,
347 &test_election_builder().build(),
348 WriteTieOrder::OnlyNonTrivial,
349 CandidateFormat::Nicknames,
350 )
351 .unwrap();
352
353 assert_eq!(
354 std::str::from_utf8(&buf).unwrap(),
355 r#"4 2
356[nick apple banana cherry date]
3571 apple 0
3582 cherry=banana date 0
3593 banana=cherry 0
3604 date=banana apple=cherry 0
3615 banana=cherry=date=apple 0
3620
363"Apple"
364"Banana"
365"Cherry"
366"Date"
367"Vegetable contest"
368"#
369 );
370 }
371
372 #[test]
373 fn test_write_blt_maybe_ties_has_ties() {
374 let mut buf = Vec::new();
375 write_blt(
376 &mut buf,
377 &test_election_builder().tie_order([1, 3, 2, 0]).build(),
378 WriteTieOrder::OnlyNonTrivial,
379 CandidateFormat::Nicknames,
380 )
381 .unwrap();
382
383 assert_eq!(
384 std::str::from_utf8(&buf).unwrap(),
385 r#"4 2
386[nick apple banana cherry date]
387[tie banana date cherry apple]
3881 apple 0
3892 cherry=banana date 0
3903 banana=cherry 0
3914 date=banana apple=cherry 0
3925 banana=cherry=date=apple 0
3930
394"Apple"
395"Banana"
396"Cherry"
397"Date"
398"Vegetable contest"
399"#
400 );
401 }
402
403 #[test]
404 fn test_write_blt_numeric_with_ties() {
405 let mut buf = Vec::new();
406 write_blt(
407 &mut buf,
408 &test_election_builder().build(),
409 WriteTieOrder::Always,
410 CandidateFormat::Numeric,
411 )
412 .unwrap();
413
414 assert_eq!(
415 std::str::from_utf8(&buf).unwrap(),
416 r#"4 2
417[tie 1 2 3 4]
4181 1 0
4192 3=2 4 0
4203 2=3 0
4214 4=2 1=3 0
4225 2=3=4=1 0
4230
424"Apple"
425"Banana"
426"Cherry"
427"Date"
428"Vegetable contest"
429"#
430 );
431 }
432
433 #[test]
434 fn test_write_blt_numeric_no_ties() {
435 let mut buf = Vec::new();
436 write_blt(
437 &mut buf,
438 &test_election_builder().build(),
439 WriteTieOrder::Never,
440 CandidateFormat::Numeric,
441 )
442 .unwrap();
443
444 assert_eq!(
445 std::str::from_utf8(&buf).unwrap(),
446 r#"4 2
4471 1 0
4482 3=2 4 0
4493 2=3 0
4504 4=2 1=3 0
4515 2=3=4=1 0
4520
453"Apple"
454"Banana"
455"Cherry"
456"Date"
457"Vegetable contest"
458"#
459 );
460 }
461}