排行榜实现
This commit is contained in:
parent
b8e510454b
commit
3010a597f8
|
@ -0,0 +1,207 @@
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
"github.com/kercylan98/minotaur/utils/synchronization"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRankingList 创建一个排行榜
|
||||||
|
func NewRankingList[CompetitorID comparable, Score generic.Ordered](options ...RankingListOption[CompetitorID, Score]) *RankingList[CompetitorID, Score] {
|
||||||
|
rankingList := &RankingList[CompetitorID, Score]{
|
||||||
|
rankCount: 100,
|
||||||
|
competitors: synchronization.NewMap[CompetitorID, Score](),
|
||||||
|
}
|
||||||
|
for _, option := range options {
|
||||||
|
option(rankingList)
|
||||||
|
}
|
||||||
|
return rankingList
|
||||||
|
}
|
||||||
|
|
||||||
|
type RankingList[CompetitorID comparable, Score generic.Ordered] struct {
|
||||||
|
asc bool
|
||||||
|
rankCount int
|
||||||
|
competitors *synchronization.Map[CompetitorID, Score]
|
||||||
|
scores [][2]any // CompetitorID, Score
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) Competitor(competitorId CompetitorID, score Score) {
|
||||||
|
v, exist := slf.competitors.GetExist(competitorId)
|
||||||
|
if exist {
|
||||||
|
if slf.Cmp(v, score) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rank, err := slf.GetRank(competitorId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slf.scores = append(slf.scores[0:rank], slf.scores[rank+1:]...)
|
||||||
|
slf.competitors.Delete(competitorId)
|
||||||
|
if slf.Cmp(score, v) > 0 {
|
||||||
|
slf.competitor(competitorId, score, 0, rank-1)
|
||||||
|
} else {
|
||||||
|
slf.competitor(competitorId, score, rank, len(slf.scores)-1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if slf.rankCount > 0 && len(slf.scores) >= slf.rankCount {
|
||||||
|
last := slf.scores[len(slf.scores)-1]
|
||||||
|
if slf.Cmp(score, last[1]) <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slf.competitor(competitorId, score, 0, len(slf.scores)-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) RemoveCompetitor(competitorId CompetitorID) {
|
||||||
|
if !slf.competitors.Exist(competitorId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rank, err := slf.GetRank(competitorId)
|
||||||
|
if err != nil {
|
||||||
|
slf.competitors.Delete(competitorId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slf.scores = append(slf.scores[0:rank], slf.scores[rank+1:]...)
|
||||||
|
slf.competitors.Delete(competitorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) GetRank(competitorId CompetitorID) (int, error) {
|
||||||
|
competitorScore, exist := slf.competitors.GetExist(competitorId)
|
||||||
|
if !exist {
|
||||||
|
return 0, ErrRankingListNotExistCompetitor
|
||||||
|
}
|
||||||
|
|
||||||
|
low, high := 0, len(slf.scores)-1
|
||||||
|
for low <= high {
|
||||||
|
mid := (low + high) / 2
|
||||||
|
data := slf.scores[mid]
|
||||||
|
id, score := data[0].(CompetitorID), data[1].(Score)
|
||||||
|
if id == competitorId {
|
||||||
|
return mid, nil
|
||||||
|
} else if slf.Cmp(score, competitorScore) == 0 {
|
||||||
|
for i := mid + 1; i <= high; i++ {
|
||||||
|
data := slf.scores[i]
|
||||||
|
if data[0].(CompetitorID) == competitorId {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := mid - 1; i >= low; i-- {
|
||||||
|
data := slf.scores[i]
|
||||||
|
if data[0].(CompetitorID) == competitorId {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if slf.Cmp(score, competitorScore) < 0 {
|
||||||
|
high = mid - 1
|
||||||
|
} else {
|
||||||
|
low = mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, ErrRankingListIndexErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) GetCompetitor(rank int) (competitorId CompetitorID, err error) {
|
||||||
|
if rank < 0 || rank >= len(slf.scores) {
|
||||||
|
return competitorId, ErrRankingListNonexistentRanking
|
||||||
|
}
|
||||||
|
return slf.scores[rank][0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) GetCompetitorWithRange(start, end int) ([]CompetitorID, error) {
|
||||||
|
if start < 1 || end < start {
|
||||||
|
return nil, ErrRankingListNonexistentRanking
|
||||||
|
}
|
||||||
|
total := len(slf.scores)
|
||||||
|
if start > total {
|
||||||
|
return nil, ErrRankingListNonexistentRanking
|
||||||
|
}
|
||||||
|
if end > total {
|
||||||
|
end = total
|
||||||
|
}
|
||||||
|
var ids []CompetitorID
|
||||||
|
for _, data := range slf.scores[start-1 : end] {
|
||||||
|
ids = append(ids, data[0])
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) GetScore(competitorId CompetitorID) (score Score, err error) {
|
||||||
|
data, ok := slf.competitors.GetExist(competitorId)
|
||||||
|
if !ok {
|
||||||
|
return score, ErrRankingListNotExistCompetitor
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) GetAllCompetitor() []CompetitorID {
|
||||||
|
return slf.competitors.Keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) Clear() {
|
||||||
|
slf.competitors.Clear()
|
||||||
|
slf.scores = make([][2]any, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) Cmp(s1, s2 Score) int {
|
||||||
|
var result int
|
||||||
|
if s1 > s2 {
|
||||||
|
result = 1
|
||||||
|
} else if s1 < s2 {
|
||||||
|
result = -1
|
||||||
|
} else {
|
||||||
|
result = 0
|
||||||
|
}
|
||||||
|
if slf.asc {
|
||||||
|
return -result
|
||||||
|
} else {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slf *RankingList[CompetitorID, Score]) competitor(competitorId CompetitorID, score Score, low, high int) {
|
||||||
|
for low <= high {
|
||||||
|
mid := (low + high) / 2
|
||||||
|
data := slf.scores[mid]
|
||||||
|
if slf.Cmp(data[1], score) == 0 {
|
||||||
|
for low = mid + 1; low <= high; low++ {
|
||||||
|
if slf.Cmp(slf.scores[low][1], score) != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if slf.Cmp(data[1], score) < 0 {
|
||||||
|
high = mid - 1
|
||||||
|
} else {
|
||||||
|
low = mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count := len(slf.scores)
|
||||||
|
if low == count {
|
||||||
|
if slf.rankCount > 0 && count >= slf.rankCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slf.scores = append(slf.scores, [2]any{competitorId, score})
|
||||||
|
slf.competitors.Set(competitorId, score)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scoreItem := [2]any{competitorId, score}
|
||||||
|
|
||||||
|
//队首
|
||||||
|
if low == 0 {
|
||||||
|
slf.scores = append([][2]any{scoreItem}, slf.scores...)
|
||||||
|
} else {
|
||||||
|
tmp := append([][2]any{scoreItem}, slf.scores[low:]...)
|
||||||
|
slf.scores = append(slf.scores[0:low], tmp...)
|
||||||
|
}
|
||||||
|
slf.competitors.Set(competitorId, score)
|
||||||
|
if slf.rankCount <= 0 || len(slf.scores) <= slf.rankCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count = len(slf.scores) - 1
|
||||||
|
scoreItem = slf.scores[count]
|
||||||
|
slf.competitors.Delete(scoreItem[0])
|
||||||
|
slf.scores = slf.scores[0:count]
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRankingListNotExistCompetitor = errors.New("ranking list not exist competitor")
|
||||||
|
ErrRankingListIndexErr = errors.New("ranking list index error")
|
||||||
|
ErrRankingListNonexistentRanking = errors.New("nonexistent ranking")
|
||||||
|
)
|
|
@ -0,0 +1,24 @@
|
||||||
|
package builtin
|
||||||
|
|
||||||
|
import "github.com/kercylan98/minotaur/utils/generic"
|
||||||
|
|
||||||
|
type RankingListOption[CompetitorID comparable, Score generic.Ordered] func(list *RankingList[CompetitorID, Score])
|
||||||
|
|
||||||
|
// WithRankingListCount 通过限制排行榜竞争者数量来创建排行榜
|
||||||
|
// - 默认情况下允许100位竞争者
|
||||||
|
func WithRankingListCount[CompetitorID comparable, Score generic.Ordered](rankCount int) RankingListOption[CompetitorID, Score] {
|
||||||
|
return func(list *RankingList[CompetitorID, Score]) {
|
||||||
|
if rankCount <= 0 {
|
||||||
|
rankCount = 1
|
||||||
|
}
|
||||||
|
list.rankCount = rankCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRankingListASC 通过升序的方式创建排行榜
|
||||||
|
// - 默认情况下为降序
|
||||||
|
func WithRankingListASC[CompetitorID comparable, Score generic.Ordered]() RankingListOption[CompetitorID, Score] {
|
||||||
|
return func(list *RankingList[CompetitorID, Score]) {
|
||||||
|
list.asc = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
// RankingList 排行榜
|
||||||
|
type RankingList[CompetitorID comparable, Score comparable] interface {
|
||||||
|
// Competitor 声明排行榜竞争者
|
||||||
|
// - 如果竞争者存在的情况下,会更新已有成绩,否则新增竞争者
|
||||||
|
Competitor(competitorId CompetitorID, score Score)
|
||||||
|
// RemoveCompetitor 删除特定竞争者
|
||||||
|
RemoveCompetitor(competitorId CompetitorID)
|
||||||
|
// GetRank 获取竞争者排名
|
||||||
|
GetRank(competitorId CompetitorID) (int, error)
|
||||||
|
// GetCompetitor 获取特定排名的竞争者
|
||||||
|
GetCompetitor(rank int) (CompetitorID, error)
|
||||||
|
// GetCompetitorWithRange 获取第start名到第end名竞争者
|
||||||
|
GetCompetitorWithRange(start, end int) ([]CompetitorID, error)
|
||||||
|
// GetScore 获取竞争者成绩
|
||||||
|
GetScore(competitorId CompetitorID) (Score, error)
|
||||||
|
// GetAllCompetitor 获取所有竞争者ID
|
||||||
|
// - 结果为名次有序的
|
||||||
|
GetAllCompetitor() []CompetitorID
|
||||||
|
// Clear 清空排行榜
|
||||||
|
Clear()
|
||||||
|
}
|
Loading…
Reference in New Issue