///|
pub(all) suberror TallyError {
Invalid(String)
Limit(String)
} derive(Eq, @debug.Debug)
///|
pub(all) struct Ballot {
weight : Int32
ranking : Array[Array[String]]
} derive(Eq, @debug.Debug)
///|
pub struct Election {
priv candidates : Array[String]
priv ballots : Array[Ballot]
priv pairwise : Array[Array[Int32]]
priv total : Int32
priv nonblank : Int32
}
///|
pub(all) struct Beatpath {
from : String
to : String
strength : Int32
path : Array[String]
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct SchulzeResult {
winners : Array[String]
strengths : Array[Array[Int32]]
witnesses : Array[Beatpath]
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct LockedEdge {
from : String
to : String
margin : Int32
winning_votes : Int32
locked : Bool
cycle_path : Array[String]
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct RankedPairsResult {
winners : Array[String]
decisions : Array[LockedEdge]
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct Round {
active : Array[String]
counts : Map[String, Int32]
continuing_weight : Int32
exhausted_weight : Int32
tied_lowest : Array[String]
eliminated : String
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct IrvResult {
winners : Array[String]
rounds : Array[Round]
needs_tiebreak : Bool
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct TieWitness {
winner : String
eliminations : Array[String]
} derive(Eq, ToJson, @debug.Debug)
///|
pub(all) struct IrvPossibilities {
possible_winners : Array[String]
explored_states : Int32
witnesses : Array[TieWitness]
} derive(Eq, ToJson, @debug.Debug)
///|
pub fn election(
candidates : Array[String],
ballots : Array[Ballot],
) -> Election raise TallyError {
guard candidates.length() > 0 &&
candidates.length() <= 32 &&
ballots.length() <= 10000 else {
raise Limit("use 1..32 candidates and at most 10000 weighted ballot types")
}
let ids = candidates.copy()
ids.sort()
let positions : Map[String, Int32] = Map([])
for i, id in ids {
guard id.length() > 0 && id.length() <= 128 && !positions.contains(id) else {
raise Invalid("candidate IDs must be nonempty and unique")
}
positions[id] = i
}
let matrix = ids.map(_ => Array::make(ids.length(), 0))
let mut total = 0
let mut nonblank = 0
let stored : Array[Ballot] = []
for ballot in ballots {
guard ballot.weight > 0 && ballot.weight <= 1000000000 - total else {
raise Invalid("positive weights must total at most 1000000000")
}
total = total + ballot.weight
if ballot.ranking.length() > 0 {
nonblank = nonblank + ballot.weight
}
let ranks = Array::make(ids.length(), 1000)
for level, group in ballot.ranking {
guard group.length() > 0 else {
raise Invalid("empty rank groups are not permitted")
}
for id in group {
guard positions.get(id) is Some(i) else {
raise Invalid("unknown ranked candidate")
}
guard ranks[i] == 1000 else {
raise Invalid("candidate repeated in ballot")
}
ranks[i] = level
}
}
for i = 0; i < ids.length(); i = i + 1 {
for j = 0; j < ids.length(); j = j + 1 {
if ranks[i] < ranks[j] {
matrix[i][j] = matrix[i][j] + ballot.weight
}
}
}
stored.push({
weight: ballot.weight,
ranking: ballot.ranking.map(group => group.copy()),
})
}
{ candidates: ids, ballots: stored, pairwise: matrix, total, nonblank, }
}
///|
pub fn Election::candidates(self : Election) -> Array[String] {
self.candidates.copy()
}
///|
pub fn Election::pairwise(self : Election) -> Array[Array[Int32]] {
self.pairwise.map(row => row.copy())
}
///|
pub fn Election::total_weight(self : Election) -> Int32 {
self.total
}
///|
pub fn Election::condorcet(self : Election) -> Array[String] {
if self.nonblank == 0 {
return []
}
let winners : Array[String] = []
for i, id in self.candidates {
let mut beats_all = true
for j = 0; j < self.candidates.length(); j = j + 1 {
if i != j && self.pairwise[i][j] <= self.pairwise[j][i] {
beats_all = false
}
}
if beats_all {
winners.push(id)
}
}
winners
}