A Golang module to rank candidates using Majority Judgment, using a score-based algorithm for performance and scalability
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Go to file
Dominique Merle c5e7aa6597
feat: add the input Tally to the Results
3 months ago
.github/workflows chore(ci): add test and coverage jobs 3 years ago
docs test(colors): code coverage for palette generation 12 months ago
judgment feat: add the input Tally to the Results 3 months ago
.tokeignore chore(config): configure tokei to ignore some files in the lines count 3 years ago
LICENSE.md fix(docs): perfunctory and apparently mandatory copyright notice 3 years ago
README.md test(colors): code coverage for palette generation 12 months ago
go.mod feat: provide a color gradient generator 2 years ago
go.sum feat: provide a color gradient generator 2 years ago

README.md

Majority Judgment for Golang

MIT Release Build Status Coverage Code Quality A+ LoC Discord Chat https://discord.gg/rAAQG9S

A Golang module to deliberate using Majority Judgment to rank proposals/candidates. Majority Judgment is a simple, subtle and fair voting system.

Features

  • score-based algorithm, for performance and scalability
  • supports billions of judgments with almost the same cost as dozens
  • supports thousands of proposals per poll
  • default judgment balancing tools: static grade, median grade

Installation

go get -u github.com/mieuxvoter/majority-judgment-library-go

It exposes the package judgment, for concision, since the repo name itself is quite long and we can't rename it.

It's a pre-release. We still have some int types that may change until v1.

Usage

Say you have the following tally:

Example of a merit profile

You can compute out the majority judgment rank of each proposal like so:

package main

import (
    "fmt"
    "github.com/mieuxvoter/majority-judgment-library-go/judgment"
    "log"
)

func main() {

    pollTally := &(judgment.PollTally{
        AmountOfJudges: 10,
        Proposals: []*judgment.ProposalTally{
            {Tally: []uint64{2, 2, 2, 2, 2}}, // Proposal A   Amount of judgments received for each grade,
            {Tally: []uint64{2, 1, 1, 1, 5}}, // Proposal B   from "worst" grade to "best" grade.
            {Tally: []uint64{2, 1, 1, 2, 4}}, // Proposal C   Make sure all tallies are balanced, that is they
            {Tally: []uint64{2, 1, 5, 0, 2}}, // Proposal D   hold the same total amount of judgments.
            {Tally: []uint64{2, 2, 2, 2, 2}}, // Proposal E   Equal proposals share the same rank.
            // …
        },
    })
    deliberator := &(judgment.MajorityJudgment{})
    result, err := deliberator.Deliberate(pollTally)

    if nil != err {
        log.Fatalf("Deliberation failed: %v", err)
    }

    // Proposals results are ordered like tallies, but Rank is available. 
    // result.Proposals[0].Rank == 4 // Proposal A
    // result.Proposals[1].Rank == 1 // Proposal B
    // result.Proposals[2].Rank == 2 // Proposal C
    // result.Proposals[3].Rank == 3 // Proposal D
    // result.Proposals[4].Rank == 4 // Proposal E

    // You may also use proposals sorted by Rank ; their initial Index is available
    // result.ProposalsSorted[0].Index == 1 // Proposal B
    // result.ProposalsSorted[1].Index == 2 // Proposal C
    // result.ProposalsSorted[2].Index == 3 // Proposal D
    // result.ProposalsSorted[3].Index == 0 // Proposal A
    // result.ProposalsSorted[4].Index == 4 // Proposal E
    
    fmt.Printf("Best Proposal Index: %d\n", result.ProposalsSorted[0].Index)

}

Balancing uneven proposals

Sometimes, some proposals receive more judgments than others, and the tallies are unbalanced. In those cases, a default judgment strategy has to be picked:

Static Default Grade

Missing (or "I don't know") judgments are considered of a grade defined in advance, usually to reject in order to incentivize proposals to be explicit and clear.

You may use PollTally.BalanceWithStaticDefault(defaultGrade) to that effect:

pollTally := &PollTally{
    AmountOfJudges: 10,
    Proposals: []*ProposalTally{
        {Tally: []uint64{2, 1, 2, 2, 1}}, // Proposal A
        {Tally: []uint64{3, 1, 3, 1, 1}}, // Proposal B
        {Tally: []uint64{0, 1, 1, 0, 0}}, // Proposal C
        // …
    },
}
defaultGrade := 0
pollTally.BalanceWithStaticDefault(defaultGrade)

// pollTally was mutated and now contains balanced proposals' tallies
// pollTally.Proposals[0].Tally == {4, 1, 2, 2, 1}  // Proposal A is now balanced 
// pollTally.Proposals[1].Tally == {4, 1, 3, 1, 1}  // Proposal B is now balanced
// pollTally.Proposals[2].Tally == {8, 1, 1, 0, 0}  // Proposal C is now balanced

Median Default Grade

Same behavior as static, but the default grade for each proposal is its median grade.

Use PollTally.BalanceWithMedianDefault().

Normalization

Not implemented yet ; would require either math/big for LCM or floating-point arithmetic This is part of why deciding on int types is so tricky.

License

MIT 🐜

Contribute

This project needs a review by Go devs. Feel free to suggest changes, report issues, make improvements, etc.

Some more information is available in docs/.