-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
simplify util_sort.go FindTopNByScoreDesc (#539)
## Purpose of Changes and their Description As part of #421 I need to add features to util_sort for getting the top list of actors by score. I also observed some ways to make the `FindTopNByScoreDesc` function simpler as part of that effort. But due to the high level of importance of this code I wanted to do a refactor of this function in an isolated manner to make sure that no changes that affect tests / state are made by the way I rewrote the function. Hence this PR ## Link(s) to Ticket(s) or Issue(s) resolved by this PR Related to PROTO-2204 ## Are these changes tested and documented? This code is a refactor and so it is tested by our existing unit tests to ensure that no logical changes have been made.
- Loading branch information
Showing
6 changed files
with
94 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,106 +1,56 @@ | ||
package actorutils | ||
|
||
import ( | ||
"container/heap" | ||
"math/rand" | ||
"sort" | ||
"slices" | ||
|
||
"github.com/allora-network/allora-chain/x/emissions/types" | ||
emissionstypes "github.com/allora-network/allora-chain/x/emissions/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// Source: https://pkg.go.dev/container/heap#Push | ||
|
||
// A structure to hold the original value and a random tiebreaker | ||
type SortableItem struct { | ||
Value Actor | ||
Weight Score | ||
Tiebreaker uint32 | ||
index int | ||
} | ||
|
||
type Actor = string | ||
type BlockHeight = int64 | ||
type Score = types.Score | ||
type TopicId = uint64 | ||
|
||
type PriorityQueue []*SortableItem | ||
|
||
func (pq PriorityQueue) Len() int { return len(pq) } | ||
|
||
func (pq PriorityQueue) Less(i, j int) bool { | ||
// We want Pop to give us the highest, not lowest, priority so we use greater than here. | ||
if pq[i].Weight.Score.Equal(pq[j].Weight.Score) { | ||
return pq[i].Tiebreaker > pq[j].Tiebreaker | ||
} | ||
|
||
return pq[i].Weight.Score.Gt(pq[j].Weight.Score) | ||
} | ||
|
||
func (pq PriorityQueue) Swap(i, j int) { | ||
pq[i], pq[j] = pq[j], pq[i] | ||
pq[i].index = i | ||
pq[j].index = j | ||
} | ||
|
||
func (pq *PriorityQueue) Push(x any) { | ||
n := len(*pq) | ||
item, ok := x.(*SortableItem) | ||
if !ok { | ||
return | ||
} | ||
item.index = n | ||
*pq = append(*pq, item) | ||
} | ||
|
||
func (pq *PriorityQueue) Pop() any { | ||
old := *pq | ||
n := len(old) | ||
item := old[n-1] | ||
old[n-1] = nil // avoid memory leak | ||
item.index = -1 // for safety | ||
*pq = old[0 : n-1] | ||
return item | ||
} | ||
|
||
// Sorts the given actors by score, desc, breaking ties randomly | ||
// Returns the top N actors as a map with the actor as the key and a boolean (True) as the value | ||
func FindTopNByScoreDesc(ctx sdk.Context, n uint64, scoresByActor map[Actor]Score, randSeed BlockHeight) ([]Actor, map[string]bool) { | ||
func FindTopNByScoreDesc( | ||
ctx sdk.Context, | ||
n uint64, | ||
scores []emissionstypes.Score, | ||
randSeed int64, | ||
) (topNActorsSorted []emissionstypes.Score, allActorsSorted []emissionstypes.Score, actorIsTop map[string]struct{}) { | ||
r := rand.New(rand.NewSource(randSeed)) //nolint:gosec // G404: Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand) | ||
queue := &PriorityQueue{} | ||
i := 0 | ||
// Extract and sort the keys | ||
keys := make([]Actor, 0, len(scoresByActor)) | ||
for actor := range scoresByActor { | ||
keys = append(keys, actor) | ||
} | ||
sort.Slice(keys, func(i, j int) bool { | ||
return keys[i] < keys[j] | ||
// in our tiebreaker, we never return that two elements are equal | ||
// so the sort function will never be called with two equal elements | ||
// so we don't care about sort stability because our tiebreaker determines the order | ||
// we also sort from largest to smallest so the sort function is inverted | ||
// from the usual smallest to largest sort order | ||
slices.SortFunc(scores, func(x, y emissionstypes.Score) int { | ||
if x.Score.Lt(y.Score) { | ||
return 1 | ||
} else if x.Score.Gt(y.Score) { | ||
return -1 | ||
} else { | ||
tiebreaker := r.Intn(2) | ||
if tiebreaker == 0 { | ||
return -1 | ||
} else { | ||
return 1 | ||
} | ||
} | ||
}) | ||
|
||
// Iterate over the sorted keys | ||
for _, actor := range keys { | ||
score := scoresByActor[actor] | ||
queue.Push(&SortableItem{actor, score, r.Uint32(), i}) | ||
i++ | ||
// which is bigger, n or the length of the scores? | ||
N := n | ||
if N > uint64(len(scores)) { | ||
N = uint64(len(scores)) | ||
} | ||
|
||
heap.Init(queue) | ||
|
||
topN := make([]Actor, 0) | ||
topNBool := make(map[string]bool) | ||
for i := uint64(0); i < n; i++ { | ||
if queue.Len() == 0 { | ||
break | ||
} | ||
item, ok := heap.Pop(queue).(*SortableItem) | ||
if !ok { | ||
ctx.Logger().Warn("Error: Could not cast to SortableItem") | ||
continue | ||
} | ||
topN = append(topN, item.Value) | ||
topNBool[item.Value] = true | ||
topNActorsSorted = make([]emissionstypes.Score, N) | ||
actorIsTop = make(map[string]struct{}, N) | ||
// populate top n actors sorted with only the top n | ||
// populate all with all | ||
// actor is top is a map of the top n actors | ||
for i := uint64(0); i < N; i++ { | ||
topNActorsSorted[i] = scores[i] | ||
actorIsTop[scores[i].Address] = struct{}{} | ||
} | ||
|
||
return topN, topNBool | ||
return topNActorsSorted, scores, actorIsTop | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters