Votee is a library of data structures and algorithms for counting votes in Elections. This library is inspired from the Agora vote counting library. It is a lightweight vote counting library implemented in scala 3.
Currently, the following vote counting algorithms are implemented:
Approval
Baldwin
Borda
Contingent
Coomb
Exhausive Ballot
Majority
Super Majority
Veto
The library is meant to be highly extensible by the user. It was designed to be used with your own components such as your own definition of a Ballot or Candidate. The library consist of the following core concepts.
Election
Ballot
Candidate
TieResolver
Winner
Each of these components are made to be extensible, however the library is provided with a default implementation of each component. This enables users to use the library or test it without implementing their own components.
Since this is a Scala 3
project and so far no Fat JAR packager library such as sbt-assembly
currently supports scala 3 projects
, developers will have to use the JAR provided in the output folder.
Follow the steps bellow to add votee to your scala application.
-
In the root directory of your scala application create a directory called
lib
-
Copy the JAR file(
votee-x.y.z.jar
) fromoutput
to yourlib
directory. -
Test the application with the following code snippet
import spire.math.Rational import votee.algorithms.Majority import votee.models.{Election, PreferentialBallot, PreferentialCandidate, TieResolver} import scala.util.Random object Main extends App { val candidates = List(PreferentialCandidate("a", "A"), PreferentialCandidate("b", "B")) val ballots = List( PreferentialBallot(1, Rational(1), Random.shuffle(candidates)), PreferentialBallot(2, Rational(1), Random.shuffle(candidates)), PreferentialBallot(3, Rational(1), Random.shuffle(candidates)), PreferentialBallot(4, Rational(1), Random.shuffle(candidates)), PreferentialBallot(5, Rational(1), Random.shuffle(candidates)), PreferentialBallot(6, Rational(1), Random.shuffle(candidates)), PreferentialBallot(7, Rational(1), Random.shuffle(candidates)), PreferentialBallot(9, Rational(1), Random.shuffle(candidates)), PreferentialBallot(10, Rational(1), Random.shuffle(candidates))) //given tieResolver: TieResolver[PreferentialCandidate] = Election.TieResolvers.randomTieResolver[PreferentialCandidate] val winner = Majority.run(ballots, candidates, 1) println(s"Winner is: ${winner}") }
-
Candidate: If the current implementation of the Candidate doesn't suit your needs you can create yours by extending the candidate class
import votee.models.Candidate final case class MyCandidate(override val id: String, name: String, ...) extends Candidate(id)
-
Ballot: You are advised not to build your own Ballot and rather used the builtin implementation which is
PreferentialBallot
. You can however extend the current implementation of Ballot if you really have to. Below is how thePreferentialBallot
is implemented.import votee.models.{Ballot, Candidate} import spire.math.Rational final case class PreferentialBallot[+C <: Candidate](override val id: Int, override val weight: Rational = Rational(1, 1), override val preferences: Seq[C]) extends Ballot[C, PreferentialBallot](id, weight, preferences) : override def --[CC >: C <: Candidate](candidates: Seq[CC]): PreferentialBallot[CC] = PreferentialBallot(id, weight, preferences.filterNot(candidates.contains(_))) override def ++[CC >: C <: Candidate](candidates: Seq[CC]): PreferentialBallot[CC] = PreferentialBallot(id, weight, candidates ++ preferences) end PreferentialBallot
-
TieResolver: There are 3 implementations of TieResolver which you can simply import from
Election.TieResolvers
. TieResolvers describe how the algorithms should resolve ties, which is absolutely necessary when running the election. Below are the different TieResolvers already implemented in the library.- DoNothingTieResolver (Default)
- RandomTieResolver
- ReverseTieResolver
If for some reason you would like to resolve ties differently from what is proposed above, you need to implement the
TieResolver trait
. Below is the contractimport spire.math.Rational import votee.models.Candidate /** * TieResolver describes the contract on how to resolve candidates with the same scores. * * Given a list of candidates with the same score return a new list with a desired ordering. * * The resolve function must satisfy the following conditions: * * 1. Total number of elements in the original list should be same as that of returned list * * 2. All elements in the original list should be found in the returned list * @tparam C */ trait TieResolver[C <: Candidate]: def resolve(candidateScores: List[(C, Rational)]): List[(C, Rational)]
#TODO