Skip to content

Commit

Permalink
Add DuelsIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
janos committed Jan 21, 2024
1 parent 5dd5056 commit c9acad0
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 24 deletions.
32 changes: 27 additions & 5 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func ExampleVoting() {
// First vote.
if _, err := v.Vote(schulze.Ballot[string]{
"A": 1,
"C": 2,
}); err != nil {
log.Fatal(err)
}
Expand All @@ -27,19 +28,40 @@ func ExampleVoting() {
if _, err := v.Vote(schulze.Ballot[string]{
"A": 1,
"B": 1,
}); err != nil {
log.Fatal(err)
}

// Third vote.
if _, err := v.Vote(schulze.Ballot[string]{
"A": 1,
"B": 2,
"C": 2,
}); err != nil {
log.Fatal(err)
}

// Calculate the result.
result, tie := v.Compute()
result, duels, tie := v.Compute()
if tie {
log.Fatal("tie")
log.Fatal("Tie")
}
fmt.Println("winner:", result[0].Choice)

// Output: winner: A
for duel := duels(); duel != nil; duel = duels() {
winner, defeated := duel.Outcome()
if winner == nil {
fmt.Printf("Options %s and %s are in tie %v\n", duel.Left.Choice, duel.Right.Choice, duel.Left.Strength)
} else {
fmt.Printf("Options %s defeats %s by (%v - %v) = %v votes\n", winner.Choice, defeated.Choice, winner.Strength, defeated.Strength, duel.Left.Strength-defeated.Strength)
}
}

fmt.Println("Winner:", result[0].Choice)

// Output: Options A defeats B by (2 - 0) = 2 votes
// Options A defeats C by (3 - 0) = 3 votes
// Options B and C are in tie 0
// Winner: A
}

func ExampleNewPreferences() {
Expand All @@ -64,7 +86,7 @@ func ExampleNewPreferences() {
}

// Calculate the result.
result, tie := schulze.Compute(preferences, choices)
result, _, tie := schulze.Compute(preferences, choices)
if tie {
log.Fatal("tie")
}
Expand Down
105 changes: 91 additions & 14 deletions schulze.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ func SetChoices[C comparable](preferences []int, current, updated []C) []int {
return updatedPreferences
}

type Choice[C comparable] struct {
// The choice value.
Value C
// 0-based ordinal number of the choice in the choice slice.
Index int
}

// Result represents a total number of wins for a single choice.
type Result[C comparable] struct {
// The choice value.
Expand All @@ -188,26 +195,96 @@ type Result[C comparable] struct {
Index int
// Number of wins in pairwise comparisons to other choices votings.
Wins int
// Total number of votes in wins in pairwise comparisons to other choices
// votings.
// Strength does not effect the winner, and may be less then the
// Strength of the choice with more wins.
// Total number of votes in the weakest link of the strongest path in wins
// in pairwise comparisons to other choices votings. Strength does not
// effect the winner, and may be less then the Strength of the choice with
// more wins.
Strength int
// Total number of preferred votes (difference between votes of the winner
// choice and the opponent choice) in wins in pairwise comparisons to other
// choices votings. Advantage does not effect the winner, and may be less
// then the Advantage of the choice with more wins. The code with less wins
// and greater Advantage had stronger but fewer wins and that information
// can be taken into the analysis of the results.
// choice and the opponent choice) in the weakest link of the strongest path
// in wins in pairwise comparisons to other choices votings. Advantage does
// not effect the winner, and may be less then the Advantage of the choice
// with more wins. The code with less wins and greater Advantage had
// stronger but fewer wins and that information can be taken into the
// analysis of the results.
Advantage int
}

// Compute calculates a sorted list of choices with the total number of wins for
// each of them by reading preferences data previously populated by the Vote
// function. If there are multiple winners, tie boolean parameter is true.
func Compute[C comparable](preferences []int, choices []C) (results []Result[C], tie bool) {
func Compute[C comparable](preferences []int, choices []C) (results []Result[C], duels DuelsIterator[C], tie bool) {
strengths := calculatePairwiseStrengths(choices, preferences)
return calculateResults(choices, strengths)
results, tie = calculateResults(choices, strengths)
return results, newDuelsIterator(choices, strengths), tie
}

// DuelsIterator is a function that returns the next Duel ordered by the choice indexes.
type DuelsIterator[C comparable] func() *Duel[C]

func newDuelsIterator[C comparable](choices []C, strengths []int) (duels DuelsIterator[C]) {
choicesCount := len(choices)
choiceIndexRow := 0
choiceIndexColumn := 1

return func() *Duel[C] {
if choiceIndexRow >= choicesCount || choiceIndexColumn >= choicesCount {
return nil
}

defer func() {
choiceIndexColumn++
if choiceIndexColumn >= choicesCount {
choiceIndexRow++
choiceIndexColumn = choiceIndexRow + 1
}
}()

return &Duel[C]{
Left: ChoiceStrength[C]{
Choice: choices[choiceIndexRow],
Index: choiceIndexRow,
Strength: strengths[choiceIndexRow*choicesCount+choiceIndexColumn],
},
Right: ChoiceStrength[C]{
Choice: choices[choiceIndexColumn],
Index: choiceIndexColumn,
Strength: strengths[choiceIndexColumn*choicesCount+choiceIndexRow],
},
}
}
}

// Duel represents a pairwise comparison between two choices that are compared
// by their strongest paths strengths (number of votes in the weakest link of
// the strongest path).
type Duel[C comparable] struct {
Left ChoiceStrength[C]
Right ChoiceStrength[C]
}

// Outcome returns the the winner and the defeated choice in the pairwise
// comparison of their strengths. If nils are returned, the outcome of the duel
// is a tie.
func (d Duel[C]) Outcome() (winner, defeated *ChoiceStrength[C]) {
if d.Left.Strength > d.Right.Strength {
return &d.Left, &d.Right
}
if d.Right.Strength > d.Left.Strength {
return &d.Right, &d.Left
}
return nil, nil // tie
}

// ChoiceStrength stores the strength of a choice. The strength is the number of
// votes in the weakest link of the strongest path between votes for different
// choices.
type ChoiceStrength[C comparable] struct {
// The choice value.
Choice C
// 0-based ordinal number of the choice in the choice slice.
Index int
Strength int
}

type choiceIndex int
Expand Down Expand Up @@ -337,7 +414,7 @@ func calculateResults[C comparable](choices []C, strengths []int) (results []Res

for i := 0; i < choicesCount; i++ {
var wins int
var popularity int
var strength int
var advantage int

for j := 0; j < choicesCount; j++ {
Expand All @@ -346,7 +423,7 @@ func calculateResults[C comparable](choices []C, strengths []int) (results []Res
sji := strengths[j*choicesCount+i]
if sij > sji {
wins++
popularity += sij
strength += sij
advantage += sij - sji
}
}
Expand All @@ -355,7 +432,7 @@ func calculateResults[C comparable](choices []C, strengths []int) (results []Res
Choice: choices[i],
Index: i,
Wins: wins,
Strength: popularity,
Strength: strength,
Advantage: advantage,
})
}
Expand Down
8 changes: 4 additions & 4 deletions schulze_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func TestVoting(t *testing.T) {
}
}

result, tie := schulze.Compute(preferences, tc.choices)
result, _, tie := schulze.Compute(preferences, tc.choices)
if tie != tc.tie {
t.Errorf("got tie %v, want %v", tie, tc.tie)
}
Expand All @@ -315,7 +315,7 @@ func TestVoting(t *testing.T) {
}
}

result, tie := v.Compute()
result, _, tie := v.Compute()
if tie != tc.tie {
t.Errorf("got tie %v, want %v", tie, tc.tie)
}
Expand Down Expand Up @@ -951,7 +951,7 @@ func BenchmarkVoting_Results(b *testing.B) {
b.ResetTimer()

for n := 0; n < b.N; n++ {
_, _ = v.Compute()
_, _, _ = v.Compute()
}
}

Expand Down Expand Up @@ -979,7 +979,7 @@ func BenchmarkResults(b *testing.B) {
b.ResetTimer()

for n := 0; n < b.N; n++ {
_, _ = schulze.Compute(preferences, choices)
_, _, _ = schulze.Compute(preferences, choices)
}
}

Expand Down
2 changes: 1 addition & 1 deletion voting.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ func (v *Voting[C]) SetChoices(updated []C) {

// Compute calculates a sorted list of choices with the total number of wins for
// each of them. If there are multiple winners, tie boolean parameter is true.
func (v *Voting[C]) Compute() (results []Result[C], tie bool) {
func (v *Voting[C]) Compute() (results []Result[C], duels DuelsIterator[C], tie bool) {
return Compute(v.preferences, v.choices)
}

0 comments on commit c9acad0

Please sign in to comment.