新增排行榜服务

This commit is contained in:
orgin
2022-11-18 15:45:20 +08:00
parent 5601ab5ae2
commit 95b4e2f8de
11 changed files with 4410 additions and 0 deletions

3000
rpc/rank.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
package rankservice
import (
"github.com/duanhf2012/origin/rpc"
"github.com/duanhf2012/origin/util/algorithms/skip"
"github.com/duanhf2012/origin/util/sync"
)
var emptyRankData RankData
var RankDataPool = sync.NewPoolEx(make(chan sync.IPoolData, 10240), func() sync.IPoolData {
var newRankData RankData
return &newRankData
})
type RankData struct {
*rpc.RankData
ref bool
compareFunc func(other skip.Comparator) int
}
func NewRankData(isDec bool, data *rpc.RankData) *RankData {
ret := RankDataPool.Get().(*RankData)
ret.compareFunc = ret.ascCompare
if isDec {
ret.compareFunc = ret.desCompare
}
ret.RankData = data
return ret
}
func ReleaseRankData(rankData *RankData) {
RankDataPool.Put(rankData)
}
func (p *RankData) Reset() {
*p = emptyRankData
}
func (p *RankData) IsRef() bool {
return p.ref
}
func (p *RankData) Ref() {
p.ref = true
}
func (p *RankData) UnRef() {
p.ref = false
}
func (p *RankData) Compare(other skip.Comparator) int {
return p.compareFunc(other)
}
func (p *RankData) GetKey() uint64 {
return p.Key
}
func (p *RankData) ascCompare(other skip.Comparator) int {
otherRankData := other.(*RankData)
if otherRankData.Key == p.Key {
return 0
}
retFlg := compareMoreThan(p.SortData, otherRankData.SortData)
if retFlg == 0 {
if p.Key > otherRankData.Key {
retFlg = 1
} else {
retFlg = -1
}
}
return retFlg
}
func (p *RankData) desCompare(other skip.Comparator) int {
otherRankData := other.(*RankData)
if otherRankData.Key == p.Key {
return 0
}
retFlg := compareMoreThan(otherRankData.SortData, p.SortData)
if retFlg == 0 {
if p.Key > otherRankData.Key {
retFlg = -1
} else {
retFlg = 1
}
}
return retFlg
}

View File

@@ -0,0 +1,52 @@
package rankservice
func transformLevel(level int32) interface{} {
switch level {
case 8:
return uint8(0)
case 16:
return uint16(0)
case 32:
return uint32(0)
case 64:
return uint64(0)
default:
return uint32(0)
}
}
func compareIsEqual(firstSortData, secondSortData []int64) bool {
firstLen := len(firstSortData)
if firstLen != len(secondSortData) {
return false
}
for i := firstLen - 1; i >= 0; i-- {
if firstSortData[i] != secondSortData[i] {
return false
}
}
return true
}
func compareMoreThan(firstSortData, secondSortData []int64) int {
firstLen := len(firstSortData)
secondLen := len(secondSortData)
minLen := firstLen
if firstLen > secondLen {
minLen = secondLen
}
for i := 0; i < minLen; i++ {
if firstSortData[i] > secondSortData[i] {
return 1
}
if firstSortData[i] < secondSortData[i] {
return -1
}
}
return 0
}

View File

@@ -0,0 +1,52 @@
package rankservice
import "github.com/duanhf2012/origin/service"
type RankDataChangeType int8
const (
RankDataNone RankDataChangeType = 0
RankDataAdd RankDataChangeType = 1 //数据插入
RankDataUpdate RankDataChangeType = 2 //数据更新
RankDataDelete RankDataChangeType = 3 //数据删除
)
type IRankSkip interface {
GetRankID() uint64
GetRankLen() uint64
}
// RankDataChangeCallBack 排行数据变化时调用
//type RankDataChangeCallBack interface {
// CB(iRankService service.IService, rankSkip IRankSkip, changeType RankDataChangeType, changed []*RankData)
//}
type IRankModule interface {
service.IModule
OnStart(mapRankSkip map[uint64]*RankSkip) error //服务开启时回调
OnEnterRank(rankSkip IRankSkip, enterData []*RankData) //进入排行
OnLeaveRank(rankSkip IRankSkip, leaveData []*RankData) //离开排行
OnChangeRankData(rankSkip IRankSkip, changeData []*RankData) //当排行数据变化时
OnStop(mapRankSkip map[uint64]*RankSkip) //服务结束时回调
}
type DefaultRankModule struct {
service.Module
}
func (dr *DefaultRankModule) OnStart(mapRankSkip map[uint64]*RankSkip) error {
return nil
}
func (dr *DefaultRankModule) OnEnterRank(rankSkip IRankSkip, enterData []*RankData) {
}
func (dr *DefaultRankModule) OnLeaveRank(rankSkip IRankSkip, leaveData []*RankData) {
}
func (dr *DefaultRankModule) OnChangeRankData(rankSkip IRankSkip, changeData []*RankData) {
}
func (dr *DefaultRankModule) OnStop(mapRankSkip map[uint64]*RankSkip) {
}

View File

@@ -0,0 +1,195 @@
package rankservice
import (
"fmt"
"github.com/duanhf2012/origin/log"
"github.com/duanhf2012/origin/node"
"github.com/duanhf2012/origin/rpc"
"github.com/duanhf2012/origin/service"
)
func init() {
node.Setup(&RankService{})
}
const PreMapRankSkipLen = 10
const ManualAddRankSkip = "RPC_ManualAddRankSkip"
const UpsetRank = "RPC_UpsetRank"
const DeleteRankDataByKey = "RPC_DeleteRankDataByKey"
const FindRankDataByKey = "RPC_FindRankDataByKey"
const FindRankDataByPos = "RPC_FindRankDataByPos"
const FindRankDataListStartTo = "RPC_FindRankDataListStartTo"
type RankService struct {
service.Service
mapRankSkip map[uint64]*RankSkip
rankModule IRankModule
}
func (rs *RankService) OnInit() error {
rs.mapRankSkip = make(map[uint64]*RankSkip, PreMapRankSkipLen)
err := rs.dealCfg()
if err != nil {
return err
}
if rs.rankModule != nil {
_, err = rs.AddModule(rs.rankModule)
if err != nil {
return err
}
} else {
rs.AddModule(&DefaultRankModule{})
}
return nil
}
func (rs *RankService) OnStart() {
rs.rankModule.OnStart(rs.mapRankSkip)
}
func (rs *RankService) OnRelease() {
rs.rankModule.OnStop(rs.mapRankSkip)
}
// 安装排行模块
func (rs *RankService) SetupRankModule(rankModule IRankModule) {
rs.rankModule = rankModule
}
// RPC_ManualAddRankSkip 提供手动添加排行榜
func (rs *RankService) RPC_ManualAddRankSkip(addInfo *rpc.AddRankList, addResult *rpc.RankResult) error {
addList := make([]uint64, 0, PreMapRankSkipLen)
for _, addRankListData := range addInfo.AddList {
if addRankListData.RankId == 0 {
rs.deleteRankList(addList)
return fmt.Errorf("RPC_AddRankSkip must has rank id")
}
newSkip := NewRankSkip(addRankListData.IsDec, transformLevel(addRankListData.SkipListLevel), addRankListData.MaxRank)
rs.mapRankSkip[addRankListData.RankId] = newSkip
addList = append(addList, addRankListData.RankId)
}
addResult.AddCount = 1
return nil
}
// RPC_UpsetRank 更新排行榜
func (rs *RankService) RPC_UpsetRank(upsetInfo *rpc.UpsetRankData, upsetResult *rpc.RankResult) error {
rankSkip, ok := rs.mapRankSkip[upsetInfo.RankId]
if ok == false || rankSkip == nil {
return fmt.Errorf("RPC_UpsetRank[", upsetInfo.RankId, "] no this rank id")
}
addCount, updateCount := rankSkip.UpsetRank(upsetInfo.RankDataList)
upsetResult.AddCount = addCount
upsetResult.ModifyCount = updateCount
return nil
}
// RPC_DeleteRankDataByKey 按排行的key进行删除
func (rs *RankService) RPC_DeleteRankDataByKey(delInfo *rpc.DeleteByKey, delResult *rpc.RankResult) error {
rankSkip, ok := rs.mapRankSkip[delInfo.RankId]
if ok == false || rankSkip == nil {
return fmt.Errorf("RPC_DeleteRankDataByKey[", delInfo.RankId, "] no this rank type")
}
removeCount := rankSkip.DeleteRankData(delInfo.KeyList)
if removeCount == 0 {
log.SError("remove count is zero")
}
delResult.RemoveCount = removeCount
return nil
}
// RPC_FindRankDataByKey 按key查找
func (rs *RankService) RPC_FindRankDataByKey(findInfo *rpc.FindRankDataByKey, findResult *rpc.RankPosData) error {
rankObj, ok := rs.mapRankSkip[findInfo.RankId]
if ok == false || rankObj == nil {
return fmt.Errorf("RPC_FindRankDataByKey[", findInfo.RankId, "] no this rank type")
}
findRankData, rankPos := rankObj.GetRankNodeData(findInfo.Key)
if findRankData != nil {
findResult.Data = findRankData.Data
findResult.Key = findRankData.Key
findResult.SortData = findRankData.SortData
findResult.RankPos = rankPos
}
return nil
}
// RPC_FindRankDataByPos 按pos查找
func (rs *RankService) RPC_FindRankDataByPos(findInfo *rpc.FindRankDataByPos, findResult *rpc.RankPosData) error {
rankObj, ok := rs.mapRankSkip[findInfo.RankId]
if ok == false || rankObj == nil {
return fmt.Errorf("RPC_FindRankDataByKey[", findInfo.RankId, "] no this rank type")
}
findRankData, rankPos := rankObj.GetRankNodeDataByPos(findInfo.Pos)
if findRankData != nil {
findResult.Data = findRankData.Data
findResult.Key = findRankData.Key
findResult.SortData = findRankData.SortData
findResult.RankPos = rankPos
}
return nil
}
// RPC_FindRankDataListStartTo 按pos查找,start开始count个排行数据
func (rs *RankService) RPC_FindRankDataListStartTo(findInfo *rpc.FindRankDataListStartTo, findResult *rpc.RankDataList) error {
rankObj, ok := rs.mapRankSkip[findInfo.RankId]
if ok == false || rankObj == nil {
return fmt.Errorf("RPC_FindRankDataListStartTo[", findInfo.RankId, "] no this rank type")
}
findResult.RankDataCount = rankObj.GetRankLen()
return rankObj.GetRankDataFromToLimit(findInfo.StartPos, findInfo.Count, findResult)
}
func (rs *RankService) deleteRankList(delIdList []uint64) {
if rs.mapRankSkip == nil {
return
}
for _, id := range delIdList {
delete(rs.mapRankSkip, id)
}
}
func (rs *RankService) dealCfg() error {
mapDBServiceCfg, ok := rs.GetServiceCfg().(map[string]interface{})
if ok == false {
return nil
}
cfgList, okList := mapDBServiceCfg["SortCfg"].([]interface{})
if okList == false {
return fmt.Errorf("RankService SortCfg must be list")
}
for _, cfg := range cfgList {
mapCfg, okCfg := cfg.(map[string]interface{})
if okCfg == false {
return fmt.Errorf("RankService SortCfg data must be map or struct")
}
rankId, okId := mapCfg["RankID"].(float64)
if okId == false {
return fmt.Errorf("RankService SortCfg data must has RankID[number]")
}
level, _ := mapCfg["SkipListLevel"].(float64)
isDec, _ := mapCfg["IsDec"].(bool)
maxRank, _ := mapCfg["MaxRank"].(float64)
newSkip := NewRankSkip(isDec, transformLevel(int32(level)), uint64(maxRank))
rs.mapRankSkip[uint64(rankId)] = newSkip
}
return nil
}

View File

@@ -0,0 +1,262 @@
package rankservice
import (
"fmt"
"github.com/duanhf2012/origin/rpc"
"github.com/duanhf2012/origin/util/algorithms/skip"
)
type RankSkip struct {
rankId uint64 //排行榜ID
isDes bool //是否为降序 true降序 false升序
skipList *skip.SkipList //跳表
mapRankData map[uint64]*RankData //排行数据map
maxLen uint64 //排行数据长度
rankModule IRankModule
}
// NewRankSkip 创建排行榜
func NewRankSkip(isDes bool, level interface{}, maxLen uint64) *RankSkip {
ret := &RankSkip{}
ret.isDes = isDes
ret.skipList = skip.New(level)
ret.mapRankData = make(map[uint64]*RankData, 10240)
ret.maxLen = maxLen
return ret
}
func (rs *RankSkip) SetupRankModule(rankModule IRankModule) {
rs.rankModule = rankModule
}
// GetRankID 获取排行榜Id
func (rs *RankSkip) GetRankID() uint64 {
return rs.rankId
}
// GetRankLen 获取排行榜长度
func (rs *RankSkip) GetRankLen() uint64 {
return rs.skipList.Len()
}
func (rs *RankSkip) UpsetRank(upsetRankData []*rpc.RankData) (addCount int32, modifyCount int32) {
addList := make([]*RankData, 0, 1)
updateList := make([]*RankData, 0, 1)
for _, upsetData := range upsetRankData {
changeData, changeType := rs.upsetRank(upsetData)
if changeData == nil {
continue
}
switch changeType {
case RankDataAdd:
addList = append(addList, changeData)
case RankDataUpdate:
updateList = append(updateList, changeData)
}
}
if len(addList) > 0 {
rs.rankModule.OnEnterRank(rs, addList)
}
if len(updateList) > 0 {
rs.rankModule.OnChangeRankData(rs, updateList)
}
addCount = int32(len(addList))
modifyCount = int32(len(updateList))
return
}
// UpsetRank 更新玩家排行数据,返回变化后的数据及变化类型
func (rs *RankSkip) upsetRank(upsetData *rpc.RankData) (*RankData, RankDataChangeType) {
rankNode, ok := rs.mapRankData[upsetData.Key]
if ok == true {
//找到的情况对比排名数据是否有变化,无变化进行data更新,有变化则进行删除更新
if compareIsEqual(rankNode.SortData, upsetData.SortData) {
rankNode.Data = upsetData.GetData()
return nil, RankDataNone
}
if upsetData.Data == nil {
upsetData.Data = rankNode.Data
}
rs.skipList.Delete(rankNode)
ReleaseRankData(rankNode)
newRankData := NewRankData(rs.isDes, upsetData)
rs.skipList.Insert(newRankData)
rs.mapRankData[upsetData.Key] = newRankData
return newRankData, RankDataUpdate
}
if rs.checkCanInsert(upsetData) {
newRankData := NewRankData(rs.isDes, upsetData)
rs.skipList.Insert(newRankData)
rs.mapRankData[upsetData.Key] = newRankData
return newRankData, RankDataAdd
}
return nil, RankDataNone
}
// DeleteRankData 删除排行数据
func (rs *RankSkip) DeleteRankData(delKeys []uint64) int32 {
removeRankData := make([]*RankData, 0, 1)
//预统计处理,进行回调
for _, key := range delKeys {
rankData, ok := rs.mapRankData[key]
if ok == false {
continue
}
removeRankData = append(removeRankData, rankData)
}
rs.rankModule.OnLeaveRank(rs, removeRankData)
//从排行榜中删除
for _, rankData := range removeRankData {
rs.skipList.Delete(rankData)
ReleaseRankData(rankData)
delete(rs.mapRankData, rankData.Key)
}
return int32(len(removeRankData))
}
// GetRankNodeData 获取,返回排名节点与名次
func (rs *RankSkip) GetRankNodeData(findKey uint64) (*RankData, uint64) {
rankNode, ok := rs.mapRankData[findKey]
if ok == false {
return nil, 0
}
_, index := rs.skipList.GetWithPosition(rankNode)
return rankNode, index
}
// GetRankNodeDataByPos 获取,返回排名节点与名次
func (rs *RankSkip) GetRankNodeDataByPos(pos uint64) (*RankData, uint64) {
rankNode := rs.skipList.ByPosition(pos)
if rankNode == nil {
return nil, 0
}
return rankNode.(*RankData), pos
}
// GetRankKeyPrevToLimit 获取key前count名的数据
func (rs *RankSkip) GetRankKeyPrevToLimit(findKey, count uint64, result *rpc.RankDataList) error {
if rs.GetRankLen() <= 0 {
return fmt.Errorf("rank[", rs.rankId, "] no data")
}
findData, ok := rs.mapRankData[findKey]
if ok == false {
return fmt.Errorf("rank[", rs.rankId, "] no data")
}
_, rankPos := rs.skipList.GetWithPosition(findData)
iter := rs.skipList.Iter(findData)
iterCount := uint64(0)
for iter.Prev() && iterCount < count {
rankData := iter.Value().(*RankData)
result.RankPosDataList = append(result.RankPosDataList, &rpc.RankPosData{
Key: rankData.Key,
RankPos: rankPos - iterCount,
SortData: rankData.SortData,
Data: rankData.Data,
})
iterCount++
}
return nil
}
// GetRankKeyPrevToLimit 获取key前count名的数据
func (rs *RankSkip) GetRankKeyNextToLimit(findKey, count uint64, result *rpc.RankDataList) error {
if rs.GetRankLen() <= 0 {
return fmt.Errorf("rank[", rs.rankId, "] no data")
}
findData, ok := rs.mapRankData[findKey]
if ok == false {
return fmt.Errorf("rank[", rs.rankId, "] no data")
}
_, rankPos := rs.skipList.GetWithPosition(findData)
iter := rs.skipList.Iter(findData)
iterCount := uint64(0)
for iter.Next() && iterCount < count {
rankData := iter.Value().(*RankData)
result.RankPosDataList = append(result.RankPosDataList, &rpc.RankPosData{
Key: rankData.Key,
RankPos: rankPos + iterCount,
SortData: rankData.SortData,
Data: rankData.Data,
})
iterCount++
}
return nil
}
// GetRankList 获取排行榜数据,startPos开始的count个数据
func (rs *RankSkip) GetRankDataFromToLimit(startPos, count uint64, result *rpc.RankDataList) error {
if rs.GetRankLen() <= 0 {
return fmt.Errorf("rank[", rs.rankId, "] no data")
}
if result.RankDataCount < startPos {
startPos = result.RankDataCount - 1
}
iter := rs.skipList.IterAtPosition(startPos)
iterCount := uint64(0)
for iter.Next() && iterCount < count {
rankData := iter.Value().(*RankData)
result.RankPosDataList = append(result.RankPosDataList, &rpc.RankPosData{
Key: rankData.Key,
RankPos: iterCount + startPos,
SortData: rankData.SortData,
Data: rankData.Data,
})
iterCount++
}
return nil
}
// checkCanInsert 检查是否能插入
func (rs *RankSkip) checkCanInsert(upsetData *rpc.RankData) bool {
//maxLen为0不限制长度
if rs.maxLen == 0 {
return true
}
//没有放满,则进行插入
rankLen := rs.skipList.Len()
if rs.maxLen > rankLen {
return true
}
//已经放满了,进行数据比较
lastPosData := rs.skipList.ByPosition(rankLen - 1)
lastRankData := lastPosData.(*RankData)
moreThanFlag := compareMoreThan(upsetData.SortData, lastRankData.SortData)
//降序排列,比最后一位小,不能插入 升序排列,比最后一位大,不能插入
if (rs.isDes == true && moreThanFlag < 0) || (rs.isDes == false && moreThanFlag > 0) || moreThanFlag == 0 {
return false
}
//移除最后一位
//回调模块该RandData从排行中删除
rs.rankModule.OnLeaveRank(rs, []*RankData{lastRankData})
rs.skipList.Delete(lastPosData)
delete(rs.mapRankData, lastRankData.Key)
ReleaseRankData(lastRankData)
return true
}

View File

@@ -0,0 +1,76 @@
syntax = "proto3";
package rpc;
option go_package = ".;rpc";
// RankData 排行数据
message RankData {
uint64 Key = 1; //数据主建
repeated int64 SortData = 2; //参与排行的数据
bytes Data = 3; //不参与排行的数据
int64 expireMs = 4; //剩余有效时间毫秒如果为0永不过期
}
// RankPosData 排行数据——查询返回
message RankPosData {
uint64 Key = 1; //数据主建
uint64 RankPos = 2; //名次
repeated int64 SortData = 3; //参与排行的数据
bytes Data = 4; //不参与排行的数据
}
// RankList 排行榜数据
message RankList {
uint64 RankId = 1; //排行榜类型
int32 SkipListLevel = 2; //排行榜level-生成的跳表的level, 8/16/32/64等
bool IsDec = 3; //不参与排行的数据
uint64 MaxRank = 4; //最大排名
}
// UpsetRankData 更新排行榜数据
message UpsetRankData {
uint64 RankId = 1; //排行榜的ID
repeated RankData RankDataList = 2; //排行数据
}
// DeleteByKey 更新排行榜数据
message DeleteByKey {
uint64 RankId = 1; //排行榜的分类ID
repeated uint64 KeyList = 2; //排行数据
}
// AddRankList 新增排行榜
message AddRankList {
repeated RankList AddList = 1; //添加的排行榜列表
}
// FindRankDataByKey 查找排行信息
message FindRankDataByKey {
uint64 RankId = 1; //排行榜的ID
uint64 Key = 2; //排行的key
}
// FindRankDataByPos 查找排行信息
message FindRankDataByPos {
uint64 RankId = 1; //排行榜的ID
uint64 Pos = 2; //排行名次
}
// FindRankDataListStartTo 查找排行信息,StartPos开始Count个
message FindRankDataListStartTo {
uint64 RankId = 1; //排行榜的ID
uint64 StartPos = 2; //排行的位置 0开始
uint64 Count = 3; //查询格式
}
// RankDataList
message RankDataList {
uint64 RankDataCount = 1; //排行长度
repeated RankPosData RankPosDataList = 2; //排行数据
}
// RankResult
message RankResult {
int32 AddCount = 1; //增加记录
int32 RemoveCount = 2; //删除数量
int32 ModifyCount = 3; //修改数量
}

View File

@@ -0,0 +1,47 @@
/*
Copyright 2014 Workiva, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package skip
// Comparator is a generic interface that represents items that can
// be compared.
type Comparator interface {
// Compare compares this interface with another. Returns a positive
// number if this interface is greater, 0 if equal, negative number
// if less.
Compare(Comparator) int
}
// Comparators is a typed list of type Comparator.
type Comparators []Comparator
// Iterator defines an interface that allows a consumer to iterate
// all results of a query. All values will be visited in-order.
type Iterator interface {
// Next returns a bool indicating if there is future value
// in the iterator and moves the iterator to that value.
Next() bool
// Prev returns a bool indicating if there is Previous value
// in the iterator and moves the iterator to that value.
Prev() bool
// Value returns a Comparator representing the iterator's current
// position. If there is no value, this returns nil.
Value() Comparator
// exhaust is a helper method that will iterate this iterator
// to completion and return a list of resulting Entries
// in order.
exhaust() Comparators
}

View File

@@ -0,0 +1,86 @@
/*
Copyright 2014 Workiva, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package skip
const iteratorExhausted = -2
// iterator represents an object that can be iterated. It will
// return false on Next and nil on Value if there are no further
// values to be iterated.
type iterator struct {
first bool
n *node
}
// Next returns a bool indicating if there are any further values
// in this iterator.
func (iter *iterator) Next() bool {
if iter.first {
iter.first = false
return iter.n != nil
}
if iter.n == nil {
return false
}
iter.n = iter.n.forward[0]
return iter.n != nil
}
// Prev returns a bool indicating if there are any Previous values
// in this iterator.
func (iter *iterator) Prev() bool {
if iter.first {
iter.first = false
return iter.n != nil
}
if iter.n == nil {
return false
}
iter.n = iter.n.preNode
return iter.n != nil && iter.n.entry != nil
}
// Value returns a Comparator representing the iterator's present
// position in the query. Returns nil if no values remain to iterate.
func (iter *iterator) Value() Comparator {
if iter.n == nil {
return nil
}
return iter.n.entry
}
// exhaust is a helper method to exhaust this iterator and return
// all remaining entries.
func (iter *iterator) exhaust() Comparators {
entries := make(Comparators, 0, 10)
for i := iter; i.Next(); {
entries = append(entries, i.Value())
}
return entries
}
// nilIterator returns an iterator that will always return false
// for Next and nil for Value.
func nilIterator() *iterator {
return &iterator{}
}

View File

@@ -0,0 +1,50 @@
/*
Copyright 2014 Workiva, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package skip
type widths []uint64
type nodes []*node
type node struct {
// forward denotes the forward pointing pointers in this
// node.
forward nodes
//zero level pre node
preNode *node
// widths keeps track of the distance between this pointer
// and the forward pointers so we can access skip list
// values by position in logarithmic time.
widths widths
// entry is the associated value with this node.
entry Comparator
}
func (n *node) Compare(e Comparator) int {
return n.entry.Compare(e)
}
// newNode will allocate and return a new node with the entry
// provided. maxLevels will determine the length of the forward
// pointer list associated with this node.
func newNode(cmp Comparator, maxLevels uint8) *node {
return &node{
entry: cmp,
forward: make(nodes, maxLevels),
widths: make(widths, maxLevels),
}
}

View File

@@ -0,0 +1,494 @@
/*
Copyright 2014 Workiva, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
Package skip defines a skiplist datastructure. That is, a data structure
that probabilistically determines relationships between keys. By doing
so, it becomes easier to program than a binary search tree but maintains
similar speeds.
Performance characteristics:
Insert: O(log n)
Search: O(log n)
Delete: O(log n)
Space: O(n)
Recently added is the capability to address, insert, and replace an
entry by position. This capability is acheived by saving the width
of the "gap" between two nodes. Searching for an item by position is
very similar to searching by value in that the same basic algorithm is
used but we are searching for width instead of value. Because this avoids
the overhead associated with Golang interfaces, operations by position
are about twice as fast as operations by value. Time complexities listed
below.
SearchByPosition: O(log n)
InsertByPosition: O(log n)
More information here: http://cglab.ca/~morin/teaching/5408/refs/p90b.pdf
Benchmarks:
BenchmarkInsert-8 2000000 930 ns/op
BenchmarkGet-8 2000000 989 ns/op
BenchmarkDelete-8 3000000 600 ns/op
BenchmarkPrepend-8 1000000 1468 ns/op
BenchmarkByPosition-8 10000000 202 ns/op
BenchmarkInsertAtPosition-8 3000000 485 ns/op
CPU profiling has shown that the most expensive thing we do here
is call Compare. A potential optimization for gets only is to
do a binary search in the forward/width lists instead of visiting
every value. We could also use generics if Golang had them and
let the consumer specify primitive types, which would speed up
these operation dramatically.
*/
package skip
import (
"math/rand"
"sync"
"sync/atomic"
"time"
)
const p = .5 // the p level defines the probability that a node
// with a value at level i also has a value at i+1. This number
// is also important in determining max level. Max level will
// be defined as L(N) where L = log base (1/p) of n where n
// is the number of items in the list and N is the number of possible
// items in the universe. If p = .5 then maxlevel = 32 is appropriate
// for uint32.
// lockedSource is an implementation of rand.Source that is safe for
// concurrent use by multiple goroutines. The code is modeled after
// https://golang.org/src/math/rand/rand.go.
type lockedSource struct {
mu sync.Mutex
src rand.Source
}
// Int63 implements the rand.Source interface.
func (ls *lockedSource) Int63() (n int64) {
ls.mu.Lock()
n = ls.src.Int63()
ls.mu.Unlock()
return
}
// Seed implements the rand.Source interface.
func (ls *lockedSource) Seed(seed int64) {
ls.mu.Lock()
ls.src.Seed(seed)
ls.mu.Unlock()
}
// generator will be the common generator to create random numbers. It
// is seeded with unix nanosecond when this line is executed at runtime,
// and only executed once ensuring all random numbers come from the same
// randomly seeded generator.
var generator = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())})
func generateLevel(maxLevel uint8) uint8 {
var level uint8
for level = uint8(1); level < maxLevel-1; level++ {
if generator.Float64() >= p {
return level
}
}
return level
}
func insertNode(sl *SkipList, n *node, cmp Comparator, pos uint64, cache nodes, posCache widths, allowDuplicate bool) Comparator {
if !allowDuplicate && n != nil && n.Compare(cmp) == 0 { // a simple update in this case
oldEntry := n.entry
n.entry = cmp
return oldEntry
}
atomic.AddUint64(&sl.num, 1)
nodeLevel := generateLevel(sl.maxLevel)
if nodeLevel > sl.level {
for i := sl.level; i < nodeLevel; i++ {
cache[i] = sl.head
}
sl.level = nodeLevel
}
nn := newNode(cmp, nodeLevel)
for i := uint8(0); i < nodeLevel; i++ {
if i == 0 {
nn.preNode = cache[i]
if cache[i].forward[i] != nil {
cache[i].forward[i].preNode = nn
}
}
nn.forward[i] = cache[i].forward[i]
cache[i].forward[i] = nn
formerWidth := cache[i].widths[i]
if formerWidth == 0 {
nn.widths[i] = 0
} else {
nn.widths[i] = posCache[i] + formerWidth + 1 - pos
}
if cache[i].forward[i] != nil {
cache[i].widths[i] = pos - posCache[i]
}
}
for i := nodeLevel; i < sl.level; i++ {
if cache[i].forward[i] == nil {
continue
}
cache[i].widths[i]++
}
return nil
}
func splitAt(sl *SkipList, index uint64) (*SkipList, *SkipList) {
right := &SkipList{}
right.maxLevel = sl.maxLevel
right.level = sl.level
right.cache = make(nodes, sl.maxLevel)
right.posCache = make(widths, sl.maxLevel)
right.head = newNode(nil, sl.maxLevel)
sl.searchByPosition(index, sl.cache, sl.posCache) // populate the cache that needs updating
for i := uint8(0); i <= sl.level; i++ {
right.head.forward[i] = sl.cache[i].forward[i]
if sl.cache[i].forward[i] != nil {
right.head.widths[i] = sl.cache[i].widths[i] - (index - sl.posCache[i])
}
sl.cache[i].widths[i] = 0
sl.cache[i].forward[i] = nil
}
right.num = sl.Len() - index // right is not in user's hands yet
atomic.AddUint64(&sl.num, -right.num)
sl.resetMaxLevel()
right.resetMaxLevel()
return sl, right
}
// Skip list is a datastructure that probabalistically determines
// relationships between nodes. This results in a structure
// that performs similarly to a BST but is much easier to build
// from a programmatic perspective (no rotations).
type SkipList struct {
maxLevel, level uint8
head *node
num uint64
// a list of nodes that can be reused, should reduce
// the number of allocations in the insert/delete case.
cache nodes
posCache widths
}
// init will initialize this skiplist. The parameter is expected
// to be of some uint type which will set this skiplist's maximum
// level.
func (sl *SkipList) init(ifc interface{}) {
switch ifc.(type) {
case uint8:
sl.maxLevel = 8
case uint16:
sl.maxLevel = 16
case uint32:
sl.maxLevel = 32
case uint64, uint:
sl.maxLevel = 64
}
sl.cache = make(nodes, sl.maxLevel)
sl.posCache = make(widths, sl.maxLevel)
sl.head = newNode(nil, sl.maxLevel)
}
func (sl *SkipList) search(cmp Comparator, update nodes, widths widths) (*node, uint64) {
if sl.Len() == 0 { // nothing in the list
return nil, 1
}
var pos uint64 = 0
var offset uint8
var alreadyChecked *node
n := sl.head
for i := uint8(0); i <= sl.level; i++ {
offset = sl.level - i
for n.forward[offset] != nil && n.forward[offset] != alreadyChecked && n.forward[offset].Compare(cmp) < 0 {
pos += n.widths[offset]
n = n.forward[offset]
}
alreadyChecked = n
if update != nil {
update[offset] = n
widths[offset] = pos
}
}
return n.forward[0], pos + 1
}
func (sl *SkipList) resetMaxLevel() {
if sl.level < 1 {
sl.level = 1
return
}
for sl.head.forward[sl.level-1] == nil && sl.level > 1 {
sl.level--
}
}
func (sl *SkipList) searchByPosition(position uint64, update nodes, widths widths) (*node, uint64) {
if sl.Len() == 0 { // nothing in the list
return nil, 1
}
if position > sl.Len() {
return nil, 1
}
var pos uint64 = 0
var offset uint8
n := sl.head
for i := uint8(0); i <= sl.level; i++ {
offset = sl.level - i
for n.forward[offset] != nil && pos+n.widths[offset] <= position {
pos += n.widths[offset]
n = n.forward[offset]
}
if update != nil {
update[offset] = n
widths[offset] = pos
}
}
return n, pos + 1
}
// Get will retrieve values associated with the keys provided. If an
// associated value could not be found, a nil is returned in its place.
// This is an O(log n) operation.
func (sl *SkipList) Get(comparators ...Comparator) Comparators {
result := make(Comparators, 0, len(comparators))
var n *node
for _, cmp := range comparators {
n, _ = sl.search(cmp, nil, nil)
if n != nil && n.Compare(cmp) == 0 {
result = append(result, n.entry)
} else {
result = append(result, nil)
}
}
return result
}
// GetWithPosition will retrieve the value with the provided key and
// return the position of that value within the list. Returns nil, 0
// if an associated value could not be found.
func (sl *SkipList) GetWithPosition(cmp Comparator) (Comparator, uint64) {
n, pos := sl.search(cmp, nil, nil)
if n == nil {
return nil, 0
}
return n.entry, pos - 1
}
// ByPosition returns the Comparator at the given position.
func (sl *SkipList) ByPosition(position uint64) Comparator {
n, _ := sl.searchByPosition(position+1, nil, nil)
if n == nil {
return nil
}
return n.entry
}
func (sl *SkipList) insert(cmp Comparator) Comparator {
n, pos := sl.search(cmp, sl.cache, sl.posCache)
return insertNode(sl, n, cmp, pos, sl.cache, sl.posCache, false)
}
// Insert will insert the provided comparators into the list. Returned
// is a list of comparators that were overwritten. This is expected to
// be an O(log n) operation.
func (sl *SkipList) Insert(comparators ...Comparator) Comparators {
overwritten := make(Comparators, 0, len(comparators))
for _, cmp := range comparators {
overwritten = append(overwritten, sl.insert(cmp))
}
return overwritten
}
func (sl *SkipList) insertAtPosition(position uint64, cmp Comparator) {
if position > sl.Len() {
position = sl.Len()
}
n, pos := sl.searchByPosition(position, sl.cache, sl.posCache)
insertNode(sl, n, cmp, pos, sl.cache, sl.posCache, true)
}
// InsertAtPosition will insert the provided Comparator at the provided position.
// If position is greater than the length of the skiplist, the Comparator
// is appended. This method bypasses order checks and checks for
// duplicates so use with caution.
func (sl *SkipList) InsertAtPosition(position uint64, cmp Comparator) {
sl.insertAtPosition(position, cmp)
}
func (sl *SkipList) replaceAtPosition(position uint64, cmp Comparator) {
n, _ := sl.searchByPosition(position+1, nil, nil)
if n == nil {
return
}
n.entry = cmp
}
// Replace at position will replace the Comparator at the provided position
// with the provided Comparator. If the provided position does not exist,
// this operation is a no-op.
func (sl *SkipList) ReplaceAtPosition(position uint64, cmp Comparator) {
sl.replaceAtPosition(position, cmp)
}
func (sl *SkipList) delete(cmp Comparator) Comparator {
n, _ := sl.search(cmp, sl.cache, sl.posCache)
if n == nil || n.Compare(cmp) != 0 {
return nil
}
atomic.AddUint64(&sl.num, ^uint64(0)) // decrement
for i := uint8(0); i <= sl.level; i++ {
if sl.cache[i].forward[i] != n {
if sl.cache[i].forward[i] != nil {
sl.cache[i].widths[i]--
}
continue
}
if i == 0 {
if n.forward[i] != nil {
n.forward[i].preNode = sl.cache[i]
}
n.preNode = nil
}
sl.cache[i].widths[i] += n.widths[i] - 1
sl.cache[i].forward[i] = n.forward[i]
}
for sl.level > 1 && sl.head.forward[sl.level-1] == nil {
sl.head.widths[sl.level] = 0
sl.level--
}
return n.entry
}
// Delete will remove the provided keys from the skiplist and return
// a list of in-order Comparators that were deleted. This is a no-op if
// an associated key could not be found. This is an O(log n) operation.
func (sl *SkipList) Delete(comparators ...Comparator) Comparators {
deleted := make(Comparators, 0, len(comparators))
for _, cmp := range comparators {
deleted = append(deleted, sl.delete(cmp))
}
return deleted
}
// Len returns the number of items in this skiplist.
func (sl *SkipList) Len() uint64 {
return atomic.LoadUint64(&sl.num)
}
func (sl *SkipList) iterAtPosition(pos uint64) *iterator {
n, _ := sl.searchByPosition(pos, nil, nil)
if n == nil || n.entry == nil {
return nilIterator()
}
return &iterator{
first: true,
n: n,
}
}
// IterAtPosition is the sister method to Iter only the user defines
// a position in the skiplist to begin iteration instead of a value.
func (sl *SkipList) IterAtPosition(pos uint64) Iterator {
return sl.iterAtPosition(pos + 1)
}
func (sl *SkipList) iter(cmp Comparator) *iterator {
n, _ := sl.search(cmp, nil, nil)
if n == nil {
return nilIterator()
}
return &iterator{
first: true,
n: n,
}
}
// Iter will return an iterator that can be used to iterate
// over all the values with a key equal to or greater than
// the key provided.
func (sl *SkipList) Iter(cmp Comparator) Iterator {
return sl.iter(cmp)
}
// SplitAt will split the current skiplist into two lists. The first
// skiplist returned is the "left" list and the second is the "right."
// The index defines the last item in the left list. If index is greater
// then the length of this list, only the left skiplist is returned
// and the right will be nil. This is a mutable operation and modifies
// the content of this list.
func (sl *SkipList) SplitAt(index uint64) (*SkipList, *SkipList) {
index++ // 0-index offset
if index >= sl.Len() {
return sl, nil
}
return splitAt(sl, index)
}
// New will allocate, initialize, and return a new skiplist.
// The provided parameter should be of type uint and will determine
// the maximum possible level that will be created to ensure
// a random and quick distribution of levels. Parameter must
// be a uint type.
func New(ifc interface{}) *SkipList {
sl := &SkipList{}
sl.init(ifc)
return sl
}