///|
/// Deterministic IRV round transcript encoding.
pub fn IrvResult::to_text(self : IrvResult) -> String {
let out = StringBuilder()
for index, round in self.rounds {
out.write_string("round \{index + 1}: active=")
out.write_string(round.active.join(","))
out.write_string("; tallies=")
for i, pair in round.tallies {
if i > 0 {
out.write_string(",")
}
out.write_string("\{pair.0}=\{pair.1}")
}
out.write_string("; exhausted=\{round.exhausted}")
match round.eliminated {
Some(id) => out.write_string("; eliminated=\{id}")
None => ()
}
match round.winner {
Some(id) => out.write_string("; winner=\{id}")
None => ()
}
out.write_string("\n")
}
match self.winner {
Some(id) => out.write_string("result: winner=\{id}\n")
None => out.write_string("result: tie\n")
}
out.to_string()
}
///|
/// Verify that a result has internally consistent round snapshots.
pub fn IrvResult::verify(
self : IrvResult,
election : Election,
) -> Result[Unit, Diagnostic] {
if self.rounds.length() == 0 {
return Err({ code: "transcript.empty", line: 0, detail: "no rounds" })
}
for round in self.rounds {
if round.active.length() == 0 {
return Err({
code: "transcript.active",
line: 0,
detail: "empty active set",
})
}
if round.active.length() != round.tallies.length() {
return Err({
code: "transcript.tallies",
line: 0,
detail: "active/tally mismatch",
})
}
if !contains_all(round.active, election.candidates()) {
return Err({
code: "transcript.candidates",
line: 0,
detail: "unknown or missing candidate",
})
}
for pair in round.tallies {
if !round.active.contains(pair.0) {
return Err({ code: "transcript.tally-name", line: 0, detail: pair.0 })
}
}
let mut sum : Int = round.exhausted
for pair in round.tallies {
if pair.1 < 0 {
return Err({ code: "transcript.negative", line: 0, detail: pair.0 })
}
sum += pair.1
}
if sum > election.total_weight() {
return Err({
code: "transcript.weight",
line: 0,
detail: "round exceeds total",
})
}
}
Ok(())
}
///|
fn contains_all(names : Array[String], candidates : Array[String]) -> Bool {
if names.length() != candidates.length() {
return false
}
for name in names {
if !candidates.contains(name) {
return false
}
}
true
}