Skip to content

Commit 2ce90c9

Browse files
committed
Implement Weighted operations
1 parent f903e8d commit 2ce90c9

4 files changed

Lines changed: 342 additions & 25 deletions

File tree

errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
// ErrEmptyChoices indicates that an operation was attempted with an empty choices slice.
1414
var ErrEmptyChoices = errors.New("schulze: choices cannot be empty")
1515

16+
// ErrInvalidWeight indicates that a voting operation was attempted with non-positive weight.
17+
var ErrInvalidWeight = errors.New("schulze: weight must be greater than zero")
18+
1619
// UnknownChoiceError indicates that a choice in a ballot is not among known choices.
1720
type UnknownChoiceError[C comparable] struct {
1821
Choice C

schulze.go

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ func (r Record[C]) Map[NewC comparable](fn func(C) NewC) Record[NewC] {
7171
return res
7272
}
7373

74-
// Vote updates the preferences passed as the first argument with the Ballot
75-
// values. A record of a complete and normalized preferences is returned that
76-
// can be used to unvote.
77-
func Vote[C comparable, N Number](preferences []N, choices []C, b Ballot[C]) (Record[C], error) {
74+
// VoteWeighted updates the preferences passed as the first argument with the Ballot
75+
// values scaled by the specified weight. A record of a complete and normalized preferences
76+
// is returned that can be used to unvote.
77+
func VoteWeighted[C comparable, N Number](preferences []N, choices []C, b Ballot[C], weight N) (Record[C], error) {
78+
if weight <= 0 {
79+
return nil, ErrInvalidWeight
80+
}
7881
choicesCount := len(choices)
7982
if len(preferences) < choicesCount*choicesCount {
8083
return nil, &InvalidPreferencesError{
@@ -94,7 +97,7 @@ func Vote[C comparable, N Number](preferences []N, choices []C, b Ballot[C]) (Re
9497
icc := int(i) * choicesCount
9598
for _, choices2 := range rest {
9699
for _, j := range choices2 {
97-
preferences[icc+int(j)] += 1
100+
preferences[icc+int(j)] += weight
98101
}
99102
}
100103
}
@@ -111,15 +114,15 @@ func Vote[C comparable, N Number](preferences []N, choices []C, b Ballot[C]) (Re
111114
if ranksLen > 0 {
112115
for _, choices1 := range ranks[:ranksLen-1] {
113116
for _, i := range choices1 {
114-
preferences[int(i)*choicesCount+int(i)] += 1
117+
preferences[int(i)*choicesCount+int(i)] += weight
115118
}
116119
}
117120
}
118121
} else {
119122
// all choices are ranked, treat diagonal values as a single not ranked
120123
// choice, deprioritizing them for all existing choices
121124
for i := range choicesCount {
122-
preferences[i*choicesCount+i] += 1
125+
preferences[i*choicesCount+i] += weight
123126
}
124127
}
125128

@@ -145,10 +148,20 @@ func Vote[C comparable, N Number](preferences []N, choices []C, b Ballot[C]) (Re
145148
return r, nil
146149
}
147150

148-
// VoteRanked updates the preferences with a pre-ranked list of choices. Each
149-
// element in rankedChoices is a slice of choices tied at that rank. Choices
150-
// omitted from rankedChoices are considered unranked.
151-
func VoteRanked[C comparable, N Number](preferences []N, choices []C, rankedChoices [][]C) (Record[C], error) {
151+
// Vote updates the preferences passed as the first argument with the Ballot
152+
// values. A record of a complete and normalized preferences is returned that
153+
// can be used to unvote.
154+
func Vote[C comparable, N Number](preferences []N, choices []C, b Ballot[C]) (Record[C], error) {
155+
return VoteWeighted(preferences, choices, b, 1)
156+
}
157+
158+
// VoteRankedWeighted updates the preferences with a pre-ranked list of choices
159+
// scaled by the specified weight. Each element in rankedChoices is a slice of
160+
// choices tied at that rank. Choices omitted from rankedChoices are considered unranked.
161+
func VoteRankedWeighted[C comparable, N Number](preferences []N, choices []C, rankedChoices [][]C, weight N) (Record[C], error) {
162+
if weight <= 0 {
163+
return nil, ErrInvalidWeight
164+
}
152165
choicesCount := len(choices)
153166
if len(preferences) < choicesCount*choicesCount {
154167
return nil, &InvalidPreferencesError{
@@ -168,7 +181,7 @@ func VoteRanked[C comparable, N Number](preferences []N, choices []C, rankedChoi
168181
icc := int(i) * choicesCount
169182
for _, choices2 := range rest {
170183
for _, j := range choices2 {
171-
preferences[icc+int(j)] += 1
184+
preferences[icc+int(j)] += weight
172185
}
173186
}
174187
}
@@ -180,13 +193,13 @@ func VoteRanked[C comparable, N Number](preferences []N, choices []C, rankedChoi
180193
if ranksLen > 0 {
181194
for _, choices1 := range ranks[:ranksLen-1] {
182195
for _, i := range choices1 {
183-
preferences[int(i)*choicesCount+int(i)] += 1
196+
preferences[int(i)*choicesCount+int(i)] += weight
184197
}
185198
}
186199
}
187200
} else {
188201
for i := range choicesCount {
189-
preferences[i*choicesCount+i] += 1
202+
preferences[i*choicesCount+i] += weight
190203
}
191204
}
192205

@@ -210,8 +223,18 @@ func VoteRanked[C comparable, N Number](preferences []N, choices []C, rankedChoi
210223
return r, nil
211224
}
212225

213-
// Unvote removes the Ballot values from the preferences.
214-
func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) error {
226+
// VoteRanked updates the preferences with a pre-ranked list of choices. Each
227+
// element in rankedChoices is a slice of choices tied at that rank. Choices
228+
// omitted from rankedChoices are considered unranked.
229+
func VoteRanked[C comparable, N Number](preferences []N, choices []C, rankedChoices [][]C) (Record[C], error) {
230+
return VoteRankedWeighted(preferences, choices, rankedChoices, 1)
231+
}
232+
233+
// UnvoteWeighted removes the Ballot values scaled by the specified weight from the preferences.
234+
func UnvoteWeighted[C comparable, N Number](preferences []N, choices []C, r Record[C], weight N) error {
235+
if weight <= 0 {
236+
return ErrInvalidWeight
237+
}
215238
choicesCount := len(choices)
216239
if len(preferences) < choicesCount*choicesCount {
217240
return &InvalidPreferencesError{
@@ -225,20 +248,38 @@ func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) e
225248
return nil
226249
}
227250

251+
var getIndex func(c C) choiceIndex
252+
if choicesCount <= 16 {
253+
getIndex = func(c C) choiceIndex {
254+
return getChoiceIndex(choices, c)
255+
}
256+
} else {
257+
indexMap := make(map[C]choiceIndex, choicesCount)
258+
for idx, c := range choices {
259+
indexMap[c] = choiceIndex(idx)
260+
}
261+
getIndex = func(c C) choiceIndex {
262+
if idx, ok := indexMap[c]; ok {
263+
return idx
264+
}
265+
return -1
266+
}
267+
}
268+
228269
for rank, choices1 := range r {
229270
rest := r[rank+1:]
230271
for _, choice1 := range choices1 {
231-
i := getChoiceIndex(choices, choice1)
272+
i := getIndex(choice1)
232273
if i < 0 {
233274
continue
234275
}
235276
for _, choices2 := range rest {
236277
for _, choice2 := range choices2 {
237-
j := getChoiceIndex(choices, choice2)
278+
j := getIndex(choice2)
238279
if j < 0 {
239280
continue
240281
}
241-
preferences[int(i)*choicesCount+int(j)] -= 1
282+
preferences[int(i)*choicesCount+int(j)] -= weight
242283
}
243284
}
244285
}
@@ -252,18 +293,18 @@ func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) e
252293
// unranked choices even if it is empty
253294
for _, choices1 := range r[:recordLength-1] {
254295
for _, choice1 := range choices1 {
255-
i := getChoiceIndex(choices, choice1)
296+
i := getIndex(choice1)
256297
if i < 0 {
257298
continue
258299
}
259-
preferences[int(i)*choicesCount+int(i)] -= 1
300+
preferences[int(i)*choicesCount+int(i)] -= weight
260301
knownChoices.set(uint64(i))
261302
rankedChoices.set(uint64(i))
262303
}
263304
}
264305
// mark the rest of the known choices in the Record
265306
for _, choice1 := range r[recordLength-1] {
266-
i := getChoiceIndex(choices, choice1)
307+
i := getIndex(choice1)
267308
if i < 0 {
268309
continue
269310
}
@@ -275,7 +316,7 @@ func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) e
275316
if rankedChoices.isSet(i) {
276317
for j := range uint64(choicesCount) {
277318
if !knownChoices.isSet(j) {
278-
preferences[int(i)*choicesCount+int(j)] -= 1
319+
preferences[int(i)*choicesCount+int(j)] -= weight
279320
}
280321
}
281322
}
@@ -284,6 +325,11 @@ func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) e
284325
return nil
285326
}
286327

328+
// Unvote removes the Ballot values from the preferences.
329+
func Unvote[C comparable, N Number](preferences []N, choices []C, r Record[C]) error {
330+
return UnvoteWeighted(preferences, choices, r, 1)
331+
}
332+
287333
// SetChoices updates the preferences passed as the first argument by changing
288334
// its values to accommodate the changes to the choices. It is required to
289335
// pass the exact choices as the second parameter and complete updated choices
@@ -572,8 +618,26 @@ func ballotRanks[C comparable](choices []C, b Ballot[C]) (ranks [][]choiceIndex,
572618
rankedChoices = newBitset(uint64(choicesLen))
573619
}
574620

621+
var getIndex func(c C) choiceIndex
622+
if choicesLen <= 16 {
623+
getIndex = func(c C) choiceIndex {
624+
return getChoiceIndex(choices, c)
625+
}
626+
} else {
627+
indexMap := make(map[C]choiceIndex, choicesLen)
628+
for i, c := range choices {
629+
indexMap[c] = choiceIndex(i)
630+
}
631+
getIndex = func(c C) choiceIndex {
632+
if idx, ok := indexMap[c]; ok {
633+
return idx
634+
}
635+
return -1
636+
}
637+
}
638+
575639
for choice, rank := range b {
576-
index := getChoiceIndex(choices, choice)
640+
index := getIndex(choice)
577641
if index < 0 {
578642
return nil, false, &UnknownChoiceError[C]{Choice: choice}
579643
}
@@ -619,6 +683,24 @@ func rankedChoicesRanks[C comparable](choices []C, rankedChoices [][]C) (ranks [
619683
choicesLen := len(choices)
620684
rankedBitset := newBitset(uint64(choicesLen))
621685

686+
var getIndex func(c C) choiceIndex
687+
if choicesLen <= 16 {
688+
getIndex = func(c C) choiceIndex {
689+
return getChoiceIndex(choices, c)
690+
}
691+
} else {
692+
indexMap := make(map[C]choiceIndex, choicesLen)
693+
for i, c := range choices {
694+
indexMap[c] = choiceIndex(i)
695+
}
696+
getIndex = func(c C) choiceIndex {
697+
if idx, ok := indexMap[c]; ok {
698+
return idx
699+
}
700+
return -1
701+
}
702+
}
703+
622704
totalRanked := 0
623705
ranks = make([][]choiceIndex, 0, len(rankedChoices)+1)
624706

@@ -628,7 +710,7 @@ func rankedChoicesRanks[C comparable](choices []C, rankedChoices [][]C) (ranks [
628710
}
629711
groupIndexes := make([]choiceIndex, 0, len(group))
630712
for _, choice := range group {
631-
idx := getChoiceIndex(choices, choice)
713+
idx := getIndex(choice)
632714
if idx < 0 {
633715
return nil, false, &UnknownChoiceError[C]{Choice: choice}
634716
}

0 commit comments

Comments
 (0)