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

113 lines
3.7 KiB

package judgment
import (
"fmt"
"sort"
)
type DeliberatorInterface interface {
Deliberate(tally *PollTally) (result *PollResult, err error)
}
type MajorityJudgment struct {
favorContestation bool // strategy for evenness of judgments ; defaults to true
}
func (mj *MajorityJudgment) Deliberate(tally *PollTally) (_ *PollResult, err error) {
// TODO: preset balancers : static, median, normalization
//// Consider missing judgments as TO_REJECT judgments
//// One reason why we need many algorithms, and options on each
//for _, candidateTally := range pollTally.Candidates {
// missing := pollTally.MaxJudgmentsAmount - candidateTally.JudgmentsAmount
// if 0 < missing {
// candidateTally.JudgmentsAmount = pollTally.MaxJudgmentsAmount
// candidateTally.Grades[0].Amount += missing
// //println("Added the missing TO REJECT judgments", missing)
// }
//}
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")
}
}
amountOfJudgments := tally.Proposals[0].CountJudgments()
for _, proposalTally := range tally.Proposals {
if amountOfJudgments != proposalTally.CountJudgments() {
return nil, fmt.Errorf("unbalanced tally: " +
"some proposals hold more judgments than others ; " +
"use one of the tally balancers or make your own")
}
}
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,
}
return result, nil
}
// See docs/score-calculus-flowchart.png
func (mj *MajorityJudgment) ComputeScore(tally *ProposalTally, favorContestation bool) (_ string, err error) {
score := ""
analysis := &ProposalAnalysis{}
amountOfJudgments := tally.CountJudgments()
amountOfDigitsForGrade := 3 // fixme: compute
amountOfDigitsForAdhesionScore := 12 // fixme: compute
mutatedTally := tally.Copy()
for range tally.Tally { // loop once per grade
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)
err := mutatedTally.RegradeJudgments(analysis.MedianGrade, analysis.SecondMedianGrade)
if nil != err {
return "", err
}
}
return score, nil
}