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.
majority-judgment-library-go/majorityjudgment.go

136 lines
4.1 KiB

package judgment
import (
"fmt"
"sort"
)
type MajorityJudgment struct {
favorContestation bool // strategy for evenness of judgments ; defaults to true
}
func (mj *MajorityJudgment) Deliberate(tally *PollTally) (_ *PollResult, err error) {
amountOfProposals := len(tally.Proposals)
if 0 == amountOfProposals {
return &PollResult{Proposals: []*ProposalResult{}}, nil
}
amountOfGrades := len(tally.Proposals[0].Tally)
for _, proposalTally := range tally.Proposals {
if amountOfGrades != len(proposalTally.Tally) {
return nil, fmt.Errorf("mishaped tally: " +
"some proposals hold more grades than others ; " +
"please provide tallies of the same shape")
}
}
maximumAmountOfJudgments := uint64(0)
for _, proposalTally := range tally.Proposals {
amountOfJudgments := proposalTally.CountJudgments()
if amountOfJudgments > maximumAmountOfJudgments {
maximumAmountOfJudgments = amountOfJudgments
}
}
amountOfJudges := tally.AmountOfJudges
if amountOfJudges < maximumAmountOfJudgments {
return nil, fmt.Errorf("incoherent tally: " +
"some proposals hold more judgments than the specified amount of judges ; " +
"perhaps you forgot to set PollTally.AmountOfJudges")
}
for proposalIndex, proposalTally := range tally.Proposals {
if amountOfJudges != proposalTally.CountJudgments() {
return nil, fmt.Errorf("unbalanced tally: "+
"a proposal (#%d) holds less judgments than there are judges ; "+
"use one of the PollTally.Balance() methods first", proposalIndex)
}
}
proposalsResults := make(ProposalsResults, 0, 16)
proposalsResultsSorted := make(ProposalsResults, 0, 16)
for proposalIndex, proposalTally := range tally.Proposals {
score, scoreErr := mj.ComputeScore(proposalTally, true)
if nil != scoreErr {
return nil, scoreErr
}
proposalResult := &ProposalResult{
Index: proposalIndex,
Score: score,
Analysis: proposalTally.Analyze(),
Rank: 0, // we set it below after the sort
}
proposalsResults = append(proposalsResults, proposalResult)
proposalsResultsSorted = append(proposalsResultsSorted, proposalResult)
}
sort.Sort(sort.Reverse(proposalsResultsSorted))
// Rule: Multiple Proposals may have the same Rank in case of perfect equality.
previousScore := ""
for proposalIndex, proposalResult := range proposalsResultsSorted {
rank := proposalIndex + 1
if (previousScore == proposalResult.Score) && (proposalIndex > 0) {
rank = proposalsResultsSorted[proposalIndex-1].Rank
}
proposalResult.Rank = rank
previousScore = proposalResult.Score
}
result := &PollResult{
Proposals: proposalsResults,
ProposalsSorted: proposalsResultsSorted,
}
return result, nil
}
// See docs/score-calculus-flowchart.png
func (mj *MajorityJudgment) ComputeScore(tally *ProposalTally, favorContestation bool) (_ string, err error) {
score := ""
analysis := &ProposalAnalysis{}
amountOfGrades := tally.CountAvailableGrades()
amountOfJudgments := tally.CountJudgments()
amountOfDigitsForGrade := countDigitsUint8(amountOfGrades)
amountOfDigitsForAdhesionScore := countDigitsUint64(amountOfJudgments * 2)
amountOfJudgmentsInt := int(amountOfJudgments)
if amountOfJudgmentsInt < 0 {
return "", fmt.Errorf("too many judgments ; see branch|fork using math/big")
}
mutatedTally := tally.Copy()
for i := uint8(0); i < amountOfGrades; i++ {
analysis.Run(mutatedTally, favorContestation)
score += fmt.Sprintf("%0"+fmt.Sprintf("%d", amountOfDigitsForGrade)+"d", analysis.MedianGrade)
// fixme: BAD → uint64 to int conversion — either move to int everywhere, or use whatever bigint Go has
score += fmt.Sprintf("%0"+fmt.Sprintf("%d", amountOfDigitsForAdhesionScore)+"d", int(amountOfJudgments)+int(analysis.SecondGroupSize)*analysis.SecondGroupSign)
regradingErr := mutatedTally.RegradeJudgments(analysis.MedianGrade, analysis.SecondMedianGrade)
if nil != regradingErr {
return "", regradingErr
}
}
return score, nil
}
func countDigitsUint8(i uint8) (count uint8) {
for i > 0 {
i = i / 10 // euclidean division
count++
}
return
}
func countDigitsUint64(i uint64) (count uint8) {
for i > 0 {
i = i / 10 // Euclid wuz hear
count++
}
return
}