diff --git a/aof/marshal.go b/aof/marshal.go index 01f96bb..41d4e90 100644 --- a/aof/marshal.go +++ b/aof/marshal.go @@ -1,14 +1,15 @@ package aof import ( + "strconv" + "time" + "github.com/hdt3213/godis/datastruct/dict" List "github.com/hdt3213/godis/datastruct/list" "github.com/hdt3213/godis/datastruct/set" SortedSet "github.com/hdt3213/godis/datastruct/sortedset" "github.com/hdt3213/godis/interface/database" "github.com/hdt3213/godis/redis/protocol" - "strconv" - "time" ) // EntityToCmd serialize data entity to redis command @@ -95,7 +96,7 @@ func zSetToCmd(key string, zset *SortedSet.SortedSet) *protocol.MultiBulkReply { args[0] = zAddCmd args[1] = []byte(key) i := 0 - zset.ForEach(int64(0), int64(zset.Len()), true, func(element *SortedSet.Element) bool { + zset.ForEachByRank(int64(0), int64(zset.Len()), true, func(element *SortedSet.Element) bool { value := strconv.FormatFloat(element.Score, 'f', -1, 64) args[2+i*2] = []byte(value) args[3+i*2] = []byte(element.Member) diff --git a/aof/rdb.go b/aof/rdb.go index 1dd04e1..7332773 100644 --- a/aof/rdb.go +++ b/aof/rdb.go @@ -168,7 +168,7 @@ func (persister *Persister) generateRDB(ctx *RewriteCtx) error { err = encoder.WriteHashMapObject(key, hash, opts...) case *SortedSet.SortedSet: var entries []*model.ZSetEntry - obj.ForEach(int64(0), obj.Len(), true, func(element *SortedSet.Element) bool { + obj.ForEachByRank(int64(0), obj.Len(), true, func(element *SortedSet.Element) bool { entries = append(entries, &model.ZSetEntry{ Member: element.Member, Score: element.Score, diff --git a/commands.md b/commands.md index 26d9205..1a0d533 100644 --- a/commands.md +++ b/commands.md @@ -97,6 +97,10 @@ - zrem - zremrangebyscore - zremrangebyrank + - zlexcount + - zrangebylex + - zremrangebylex + - zrevrangebylex - Pub / Sub - publish - subscribe diff --git a/database/geo.go b/database/geo.go index 77fc45d..84ab427 100644 --- a/database/geo.go +++ b/database/geo.go @@ -2,13 +2,14 @@ package database import ( "fmt" + "strconv" + "strings" + "github.com/hdt3213/godis/datastruct/sortedset" "github.com/hdt3213/godis/interface/redis" "github.com/hdt3213/godis/lib/geohash" "github.com/hdt3213/godis/lib/utils" "github.com/hdt3213/godis/redis/protocol" - "strconv" - "strings" ) // execGeoAdd add a location into SortedSet @@ -253,7 +254,7 @@ func geoRadius0(sortedSet *sortedset.SortedSet, lat float64, lng float64, radius for _, area := range areas { lower := &sortedset.ScoreBorder{Value: float64(area[0])} upper := &sortedset.ScoreBorder{Value: float64(area[1])} - elements := sortedSet.RangeByScore(lower, upper, 0, -1, true) + elements := sortedSet.Range(lower, upper, 0, -1, true) for _, elem := range elements { members = append(members, []byte(elem.Member)) } diff --git a/database/sortedset.go b/database/sortedset.go index cb7c427..9602c1a 100644 --- a/database/sortedset.go +++ b/database/sortedset.go @@ -1,13 +1,15 @@ package database import ( + "math" + "strconv" + "strings" + SortedSet "github.com/hdt3213/godis/datastruct/sortedset" "github.com/hdt3213/godis/interface/database" "github.com/hdt3213/godis/interface/redis" "github.com/hdt3213/godis/lib/utils" "github.com/hdt3213/godis/redis/protocol" - "strconv" - "strings" ) func (db *DB) getAsSortedSet(key string) (*SortedSet.SortedSet, protocol.ErrorReply) { @@ -253,7 +255,7 @@ func range0(db *DB, key string, start int64, stop int64, withScores bool, desc b } // assert: start in [0, size - 1], stop in [start, size] - slice := sortedSet.Range(start, stop, desc) + slice := sortedSet.RangeByRank(start, stop, desc) if withScores { result := make([][]byte, len(slice)*2) i := 0 @@ -298,13 +300,13 @@ func execZCount(db *DB, args [][]byte) redis.Reply { return protocol.MakeIntReply(0) } - return protocol.MakeIntReply(sortedSet.Count(min, max)) + return protocol.MakeIntReply(sortedSet.RangeCount(min, max)) } /* * param limit: limit < 0 means no limit */ -func rangeByScore0(db *DB, key string, min *SortedSet.ScoreBorder, max *SortedSet.ScoreBorder, offset int64, limit int64, withScores bool, desc bool) redis.Reply { +func rangeByScore0(db *DB, key string, min SortedSet.Border, max SortedSet.Border, offset int64, limit int64, withScores bool, desc bool) redis.Reply { // get data sortedSet, errReply := db.getAsSortedSet(key) if errReply != nil { @@ -314,7 +316,7 @@ func rangeByScore0(db *DB, key string, min *SortedSet.ScoreBorder, max *SortedSe return &protocol.EmptyMultiBulkReply{} } - slice := sortedSet.RangeByScore(min, max, offset, limit, desc) + slice := sortedSet.Range(min, max, offset, limit, desc) if withScores { result := make([][]byte, len(slice)*2) i := 0 @@ -456,7 +458,7 @@ func execZRemRangeByScore(db *DB, args [][]byte) redis.Reply { return &protocol.EmptyMultiBulkReply{} } - removed := sortedSet.RemoveByScore(min, max) + removed := sortedSet.RemoveRange(min, max) if removed > 0 { db.addAof(utils.ToCmdLine3("zremrangebyscore", args...)) } @@ -621,6 +623,179 @@ func undoZIncr(db *DB, args [][]byte) []CmdLine { return rollbackZSetFields(db, key, field) } +func execZLexCount(db *DB, args [][]byte) redis.Reply { + key := string(args[0]) + sortedSet, errReply := db.getAsSortedSet(key) + if errReply != nil { + return errReply + } + if sortedSet == nil { + return protocol.MakeIntReply(0) + } + + minEle, maxEle := string(args[1]), string(args[2]) + min, err := SortedSet.ParseLexBorder(minEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + max, err := SortedSet.ParseLexBorder(maxEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + + count := sortedSet.RangeCount(min, max) + + return protocol.MakeIntReply(count) +} + +func execZRangeByLex(db *DB, args [][]byte) redis.Reply { + n := len(args) + if n > 3 && strings.ToLower(string(args[3])) != "limit" { + return protocol.MakeErrReply("ERR syntax error") + } + if n != 3 && n != 6 { + return protocol.MakeErrReply("ERR wrong number of arguments for 'zrangebylex' command") + } + + key := string(args[0]) + sortedSet, errReply := db.getAsSortedSet(key) + if errReply != nil { + return errReply + } + if sortedSet == nil { + return protocol.MakeIntReply(0) + } + + minEle, maxEle := string(args[1]), string(args[2]) + min, err := SortedSet.ParseLexBorder(minEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + max, err := SortedSet.ParseLexBorder(maxEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + + offset := int64(0) + limitCnt := int64(math.MaxInt64) + if n > 3 { + var err error + offset, err = strconv.ParseInt(string(args[4]), 10, 64) + if err != nil { + return protocol.MakeErrReply("ERR value is not an integer or out of range") + } + if offset < 0 { + return protocol.MakeEmptyMultiBulkReply() + } + count, err := strconv.ParseInt(string(args[5]), 10, 64) + if err != nil { + return protocol.MakeErrReply("ERR value is not an integer or out of range") + } + if count >= 0 { + limitCnt = count + } + } + + elements := sortedSet.Range(min, max, offset, limitCnt, false) + result := make([][]byte, 0, len(elements)) + for _, ele := range elements { + result = append(result, []byte(ele.Member)) + } + if len(result) == 0 { + return protocol.MakeEmptyMultiBulkReply() + } + return protocol.MakeMultiBulkReply(result) +} + +func execZRemRangeByLex(db *DB, args [][]byte) redis.Reply { + n := len(args) + if n != 3 { + return protocol.MakeErrReply("ERR wrong number of arguments for 'zremrangebylex' command") + } + + key := string(args[0]) + sortedSet, errReply := db.getAsSortedSet(key) + if errReply != nil { + return errReply + } + if sortedSet == nil { + return protocol.MakeIntReply(0) + } + + minEle, maxEle := string(args[1]), string(args[2]) + min, err := SortedSet.ParseLexBorder(minEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + max, err := SortedSet.ParseLexBorder(maxEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + + count := sortedSet.RemoveRange(min, max) + + return protocol.MakeIntReply(count) +} + +func execZRevRangeByLex(db *DB, args [][]byte) redis.Reply { + n := len(args) + if n > 3 && strings.ToLower(string(args[3])) != "limit" { + return protocol.MakeErrReply("ERR syntax error") + } + if n != 3 && n != 6 { + return protocol.MakeErrReply("ERR wrong number of arguments for 'zrangebylex' command") + } + + key := string(args[0]) + sortedSet, errReply := db.getAsSortedSet(key) + if errReply != nil { + return errReply + } + if sortedSet == nil { + return protocol.MakeIntReply(0) + } + + minEle, maxEle := string(args[2]), string(args[1]) + min, err := SortedSet.ParseLexBorder(minEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + max, err := SortedSet.ParseLexBorder(maxEle) + if err != nil { + return protocol.MakeErrReply(err.Error()) + } + + offset := int64(0) + limitCnt := int64(math.MaxInt64) + if n > 3 { + var err error + offset, err = strconv.ParseInt(string(args[4]), 10, 64) + if err != nil { + return protocol.MakeErrReply("ERR value is not an integer or out of range") + } + if offset < 0 { + return protocol.MakeEmptyMultiBulkReply() + } + count, err := strconv.ParseInt(string(args[5]), 10, 64) + if err != nil { + return protocol.MakeErrReply("ERR value is not an integer or out of range") + } + if count >= 0 { + limitCnt = count + } + } + + elements := sortedSet.Range(min, max, offset, limitCnt, true) + result := make([][]byte, 0, len(elements)) + for _, ele := range elements { + result = append(result, []byte(ele.Member)) + } + if len(result) == 0 { + return protocol.MakeEmptyMultiBulkReply() + } + return protocol.MakeMultiBulkReply(result) +} + func init() { registerCommand("ZAdd", execZAdd, writeFirstKey, undoZAdd, -4, flagWrite). attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1) @@ -652,4 +827,12 @@ func init() { attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1) registerCommand("ZRemRangeByRank", execZRemRangeByRank, writeFirstKey, rollbackFirstKey, 4, flagWrite). attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1) + registerCommand("ZLexCount", execZLexCount, readFirstKey, nil, 4, flagReadOnly). + attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1) + registerCommand("ZRangeByLex", execZRangeByLex, readFirstKey, nil, -4, flagReadOnly). + attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1) + registerCommand("ZRemRangeByLex", execZRemRangeByLex, writeFirstKey, rollbackFirstKey, 4, flagWrite). + attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1) + registerCommand("ZRevRangeByLex", execZRevRangeByLex, readFirstKey, nil, -4, flagReadOnly). + attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1) } diff --git a/database/sortedset_test.go b/database/sortedset_test.go index f9385e6..0ae854d 100644 --- a/database/sortedset_test.go +++ b/database/sortedset_test.go @@ -1,11 +1,12 @@ package database import ( - "github.com/hdt3213/godis/lib/utils" - "github.com/hdt3213/godis/redis/protocol/asserts" "math/rand" "strconv" "testing" + + "github.com/hdt3213/godis/lib/utils" + "github.com/hdt3213/godis/redis/protocol/asserts" ) func TestZAdd(t *testing.T) { @@ -321,3 +322,444 @@ func TestZPopMin(t *testing.T) { result = testDB.Exec(nil, utils.ToCmdLine("ZPopMin", key+"2", "2")) asserts.AssertErrReply(t, result, "WRONGTYPE Operation against a key holding the wrong kind of value") } + +func TestZLexCount(t *testing.T) { + testDB.Flush() + key := utils.RandString(10) + // a b c d e + result := testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "e", "0", "d", "0", "c", "0", "b", "0", "a")) + asserts.AssertNotError(t, result) + + // case1 + result1 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(-", "(+")) + asserts.AssertIntReply(t, result1, 0) + + // case2 + result2 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(-", "(g")) + asserts.AssertIntReply(t, result2, 5) + + // case3 + result3 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(-", "(c")) + asserts.AssertIntReply(t, result3, 2) + + // case4 + result4 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(-", "[c")) + asserts.AssertIntReply(t, result4, 3) + + // case5 + result5 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(a", "(+")) + asserts.AssertIntReply(t, result5, 0) + + // case6 + result6 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[-", "[+")) + asserts.AssertIntReply(t, result6, 0) + + // case + result7 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[-", "(g")) + asserts.AssertIntReply(t, result7, 5) + + // case8 + result8 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[-", "(c")) + asserts.AssertIntReply(t, result8, 2) + + // case9 + result9 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[-", "[c")) + asserts.AssertIntReply(t, result9, 3) + + // case10 + result10 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(a", "[+")) + asserts.AssertIntReply(t, result10, 0) + + // case11 + result11 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "-", "+")) + asserts.AssertIntReply(t, result11, 5) + + // case12 + result12 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "-", "(c")) + asserts.AssertIntReply(t, result12, 2) + + // case13 + result13 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "-", "[c")) + asserts.AssertIntReply(t, result13, 3) + + // case14 + result14 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(aa", "(c")) + asserts.AssertIntReply(t, result14, 1) + + // case15 + result15 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(aa", "[c")) + asserts.AssertIntReply(t, result15, 2) + + // case16 + result16 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[aa", "(c")) + asserts.AssertIntReply(t, result16, 1) + + // case17 + result17 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[aa", "[c")) + asserts.AssertIntReply(t, result17, 2) + + // case18 + result18 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(a", "(ee")) + asserts.AssertIntReply(t, result18, 4) + + // case19 + result19 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(a", "[ee")) + asserts.AssertIntReply(t, result19, 4) + + // case20 + result20 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[a", "(ee")) + asserts.AssertIntReply(t, result20, 5) + + // case21 + result21 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[a", "[ee")) + asserts.AssertIntReply(t, result21, 5) + + // case22 + result22 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(aa", "(ee")) + asserts.AssertIntReply(t, result22, 4) + + // case23 + result23 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "(aa", "[ee")) + asserts.AssertIntReply(t, result23, 4) + + // case24 + result24 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[aa", "(ee")) + asserts.AssertIntReply(t, result24, 4) + + // case25 + result25 := testDB.Exec(nil, utils.ToCmdLine("ZLexCount", key, "[aa", "[ee")) + asserts.AssertIntReply(t, result25, 4) +} + +func TestZRangeByLex(t *testing.T) { + testDB.Flush() + key := utils.RandString(10) + // a b c d e + result := testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "e", "0", "d", "0", "c", "0", "b", "0", "a")) + asserts.AssertNotError(t, result) + + // case1 + result1 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")) + asserts.AssertMultiBulkReply(t, result1, []string{"a", "b", "c", "d", "e"}) + + // case2 + result2 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "(z")) + asserts.AssertMultiBulkReply(t, result2, []string{"a", "b", "c", "d", "e"}) + + // case3 + result3 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(-", "[z")) + asserts.AssertMultiBulkReply(t, result3, []string{"a", "b", "c", "d", "e"}) + + // case4 + result4 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[a", "[e")) + asserts.AssertMultiBulkReply(t, result4, []string{"a", "b", "c", "d", "e"}) + + // case5 + result5 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "[e")) + asserts.AssertMultiBulkReply(t, result5, []string{"b", "c", "d", "e"}) + + // case6 + result6 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[a", "(e")) + asserts.AssertMultiBulkReply(t, result6, []string{"a", "b", "c", "d"}) + + // case7 + result7 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(e")) + asserts.AssertMultiBulkReply(t, result7, []string{"b", "c", "d"}) + + // case8 + result8 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(aa", "(ee")) + asserts.AssertMultiBulkReply(t, result8, []string{"b", "c", "d", "e"}) + + // case9 + result9 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(aa", "[ee")) + asserts.AssertMultiBulkReply(t, result9, []string{"b", "c", "d", "e"}) + + // case10 + result10 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[aa", "(ee")) + asserts.AssertMultiBulkReply(t, result10, []string{"b", "c", "d", "e"}) + + // case11 + result11 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[aa", "[ee")) + asserts.AssertMultiBulkReply(t, result11, []string{"b", "c", "d", "e"}) + + // case12 + result12 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(aa", "(e")) + asserts.AssertMultiBulkReply(t, result12, []string{"b", "c", "d"}) + + // case13 + result13 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(aa", "[e")) + asserts.AssertMultiBulkReply(t, result13, []string{"b", "c", "d", "e"}) + + // case14 + result14 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[aa", "(e")) + asserts.AssertMultiBulkReply(t, result14, []string{"b", "c", "d"}) + + // case15 + result15 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[aa", "[e")) + asserts.AssertMultiBulkReply(t, result15, []string{"b", "c", "d", "e"}) + + // case16 + result16 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(ee")) + asserts.AssertMultiBulkReply(t, result16, []string{"b", "c", "d", "e"}) + + // case17 + result17 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "[ee")) + asserts.AssertMultiBulkReply(t, result17, []string{"b", "c", "d", "e"}) + + // case18 + result18 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[a", "(ee")) + asserts.AssertMultiBulkReply(t, result18, []string{"a", "b", "c", "d", "e"}) + + // case19 + result19 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[a", "[ee")) + asserts.AssertMultiBulkReply(t, result19, []string{"a", "b", "c", "d", "e"}) + + // case20 + result20 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(-", "(+")) + asserts.AssertMultiBulkReplySize(t, result20, 0) + + // case21 + result21 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(+")) + asserts.AssertMultiBulkReplySize(t, result21, 0) + + // case22 + result22 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[-", "[+")) + asserts.AssertMultiBulkReplySize(t, result22, 0) + + // case23 + result23 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(z", "(g")) + asserts.AssertMultiBulkReplySize(t, result23, 0) + + // case24 + result24 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[-", "(g")) + asserts.AssertMultiBulkReply(t, result24, []string{"a", "b", "c", "d", "e"}) + + // case25 + result25 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[-", "(c")) + asserts.AssertMultiBulkReply(t, result25, []string{"a", "b"}) + + // case26 + result26 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[-", "[c")) + asserts.AssertMultiBulkReply(t, result26, []string{"a", "b", "c"}) + + // case27 + result27 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(e", "limit", "0", "-1")) + asserts.AssertMultiBulkReply(t, result27, []string{"b", "c", "d"}) + + // case28 + result28 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(e", "limit", "0", "1")) + asserts.AssertMultiBulkReply(t, result28, []string{"b"}) + + // case28 + result29 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "(a", "(e", "limit", "-1", "1")) + asserts.AssertMultiBulkReplySize(t, result29, 0) + + // case30 + result30 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "[a", "[e", "limit", "2", "100")) + asserts.AssertMultiBulkReply(t, result30, []string{"c", "d", "e"}) + + // case30 + result31 := testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+", "limit", "2", "2")) + asserts.AssertMultiBulkReply(t, result31, []string{"c", "d"}) + +} + +func TestZRemRangeByLex(t *testing.T) { + testDB.Flush() + key := utils.RandString(10) + // a b c d e + asserts.AssertNotError(t, testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "e", "0", "d", "0", "c", "0", "b", "0", "a"))) + + // case1 + asserts.AssertIntReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRemRangeByLex", key, "-", "+")), + 5) + + asserts.AssertMultiBulkReplySize(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + 0) + + // case2 + testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "e", "0", "d", "0", "c", "0", "b", "0", "a")) + + asserts.AssertIntReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRemRangeByLex", key, "-", "[c")), + 3) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"d", "e"}) + + // case3 + testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "a", "0", "b", "0", "c")) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"a", "b", "c", "d", "e"}) + + asserts.AssertIntReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRemRangeByLex", key, "(c", "+")), + 2) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"a", "b", "c"}) + + // case4 + testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "d", "0", "e")) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"a", "b", "c", "d", "e"}) + + asserts.AssertIntReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRemRangeByLex", key, "(a", "(d")), + 2) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"a", "d", "e"}) + + // case5 + testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "b", "0", "c")) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"a", "b", "c", "d", "e"}) + + asserts.AssertIntReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRemRangeByLex", key, "[a", "[d")), + 4) + + asserts.AssertMultiBulkReply(t, + testDB.Exec(nil, utils.ToCmdLine("ZRangeByLex", key, "-", "+")), + []string{"e"}) +} + +func TestZRevRangeByLex(t *testing.T) { + testDB.Flush() + key := utils.RandString(10) + // a b c d e + result := testDB.Exec(nil, utils.ToCmdLine("ZAdd", key, "0", "e", "0", "d", "0", "c", "0", "b", "0", "a")) + asserts.AssertNotError(t, result) + + // case1 + result1 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "+", "-")) + asserts.AssertMultiBulkReply(t, result1, []string{"e", "d", "c", "b", "a"}) + + // case2 + result2 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(z", "-")) + asserts.AssertMultiBulkReply(t, result2, []string{"e", "d", "c", "b", "a"}) + + // case3 + result3 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[z", "-")) + asserts.AssertMultiBulkReply(t, result3, []string{"e", "d", "c", "b", "a"}) + + // case4 + result4 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[e", "[a")) + asserts.AssertMultiBulkReply(t, result4, []string{"e", "d", "c", "b", "a"}) + + // case5 + result5 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[e", "(a")) + asserts.AssertMultiBulkReply(t, result5, []string{"e", "d", "c", "b"}) + + // case6 + result6 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "[a")) + asserts.AssertMultiBulkReply(t, result6, []string{"d", "c", "b", "a"}) + + // case7 + result7 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "(a")) + asserts.AssertMultiBulkReply(t, result7, []string{"d", "c", "b"}) + + // case8 + result8 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(ee", "(aa")) + asserts.AssertMultiBulkReply(t, result8, []string{"e", "d", "c", "b"}) + + // case9 + result9 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[ee", "(aa")) + asserts.AssertMultiBulkReply(t, result9, []string{"e", "d", "c", "b"}) + + // case10 + result10 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(ee", "[aa")) + asserts.AssertMultiBulkReply(t, result10, []string{"e", "d", "c", "b"}) + + // case11 + result11 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[ee", "[aa")) + asserts.AssertMultiBulkReply(t, result11, []string{"e", "d", "c", "b"}) + + // case12 + result12 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "(aa")) + asserts.AssertMultiBulkReply(t, result12, []string{"d", "c", "b"}) + + // case13 + result13 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[e", "(aa")) + asserts.AssertMultiBulkReply(t, result13, []string{"e", "d", "c", "b"}) + + // case14 + result14 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "[aa")) + asserts.AssertMultiBulkReply(t, result14, []string{"d", "c", "b"}) + + // case15 + result15 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[e", "[aa")) + asserts.AssertMultiBulkReply(t, result15, []string{"e", "d", "c", "b"}) + + // case16 + result16 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(ee", "(a")) + asserts.AssertMultiBulkReply(t, result16, []string{"e", "d", "c", "b"}) + + // case17 + result17 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[ee", "(a")) + asserts.AssertMultiBulkReply(t, result17, []string{"e", "d", "c", "b"}) + + // case18 + result18 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(ee", "[a")) + asserts.AssertMultiBulkReply(t, result18, []string{"e", "d", "c", "b", "a"}) + + // case19 + result19 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[ee", "[a")) + asserts.AssertMultiBulkReply(t, result19, []string{"e", "d", "c", "b", "a"}) + + // case20 + result20 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(+", "(-")) + asserts.AssertMultiBulkReplySize(t, result20, 0) + + // case21 + result21 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(+", "(a")) + asserts.AssertMultiBulkReplySize(t, result21, 0) + + // case22 + result22 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[+", "[-")) + asserts.AssertMultiBulkReplySize(t, result22, 0) + + // case23 + result23 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(g", "[-")) + asserts.AssertMultiBulkReply(t, result23, []string{"e", "d", "c", "b", "a"}) + + // case24 + result24 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(c", "[-")) + asserts.AssertMultiBulkReply(t, result24, []string{"b", "a"}) + + // case25 + result25 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[c", "[-")) + asserts.AssertMultiBulkReply(t, result25, []string{"c", "b", "a"}) + + // case26 + result26 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "(a", "limit", "0", "-1")) + asserts.AssertMultiBulkReply(t, result26, []string{"d", "c", "b"}) + + // case27 + result27 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "(a", "limit", "0", "1")) + asserts.AssertMultiBulkReply(t, result27, []string{"d"}) + + // case28 + result28 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "(e", "(a", "limit", "-1", "1")) + asserts.AssertMultiBulkReplySize(t, result28, 0) + + // case29 + result29 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "[e", "[a", "limit", "2", "100")) + asserts.AssertMultiBulkReply(t, result29, []string{"c", "b", "a"}) + + // case30 + result30 := testDB.Exec(nil, utils.ToCmdLine("ZRevRangeByLex", key, "+", "-", "limit", "2", "2")) + asserts.AssertMultiBulkReply(t, result30, []string{"c", "b"}) +} diff --git a/datastruct/sortedset/border.go b/datastruct/sortedset/border.go index c923fc1..886629c 100644 --- a/datastruct/sortedset/border.go +++ b/datastruct/sortedset/border.go @@ -14,10 +14,20 @@ import ( */ const ( - negativeInf int8 = -1 - positiveInf int8 = 1 + scoreNegativeInf int8 = -1 + scorePositiveInf int8 = 1 + lexNegativeInf int8 = '-' + lexPositiveInf int8 = '+' ) +type Border interface { + greater(element *Element) bool + less(element *Element) bool + getValue() interface{} + getExclude() bool + isIntersected(max Border) bool +} + // ScoreBorder represents range of a float value, including: <, <=, >, >=, +inf, -inf type ScoreBorder struct { Inf int8 @@ -27,10 +37,11 @@ type ScoreBorder struct { // if max.greater(score) then the score is within the upper border // do not use min.greater() -func (border *ScoreBorder) greater(value float64) bool { - if border.Inf == negativeInf { +func (border *ScoreBorder) greater(element *Element) bool { + value := element.Score + if border.Inf == scoreNegativeInf { return false - } else if border.Inf == positiveInf { + } else if border.Inf == scorePositiveInf { return true } if border.Exclude { @@ -39,10 +50,11 @@ func (border *ScoreBorder) greater(value float64) bool { return border.Value >= value } -func (border *ScoreBorder) less(value float64) bool { - if border.Inf == negativeInf { +func (border *ScoreBorder) less(element *Element) bool { + value := element.Score + if border.Inf == scoreNegativeInf { return true - } else if border.Inf == positiveInf { + } else if border.Inf == scorePositiveInf { return false } if border.Exclude { @@ -51,21 +63,29 @@ func (border *ScoreBorder) less(value float64) bool { return border.Value <= value } -var positiveInfBorder = &ScoreBorder{ - Inf: positiveInf, +func (border *ScoreBorder) getValue() interface{} { + return border.Value } -var negativeInfBorder = &ScoreBorder{ - Inf: negativeInf, +func (border *ScoreBorder) getExclude() bool { + return border.Exclude +} + +var scorePositiveInfBorder = &ScoreBorder{ + Inf: scorePositiveInf, +} + +var scoreNegativeInfBorder = &ScoreBorder{ + Inf: scoreNegativeInf, } // ParseScoreBorder creates ScoreBorder from redis arguments -func ParseScoreBorder(s string) (*ScoreBorder, error) { +func ParseScoreBorder(s string) (Border, error) { if s == "inf" || s == "+inf" { - return positiveInfBorder, nil + return scorePositiveInfBorder, nil } if s == "-inf" { - return negativeInfBorder, nil + return scoreNegativeInfBorder, nil } if s[0] == '(' { value, err := strconv.ParseFloat(s[1:], 64) @@ -88,3 +108,93 @@ func ParseScoreBorder(s string) (*ScoreBorder, error) { Exclude: false, }, nil } + +func (border *ScoreBorder) isIntersected(max Border) bool { + minValue := border.Value + maxValue := max.(*ScoreBorder).Value + return minValue > maxValue || (minValue == maxValue && (border.getExclude() || max.getExclude())) +} + +// LexBorder represents range of a string value, including: <, <=, >, >=, +, - +type LexBorder struct { + Inf int8 + Value string + Exclude bool +} + +// if max.greater(lex) then the lex is within the upper border +// do not use min.greater() +func (border *LexBorder) greater(element *Element) bool { + value := element.Member + if border.Inf == lexNegativeInf { + return false + } else if border.Inf == lexPositiveInf { + return true + } + if border.Exclude { + return border.Value > value + } + return border.Value >= value +} + +func (border *LexBorder) less(element *Element) bool { + value := element.Member + if border.Inf == lexNegativeInf { + return true + } else if border.Inf == lexPositiveInf { + return false + } + if border.Exclude { + return border.Value < value + } + return border.Value <= value +} + +func (border *LexBorder) getValue() interface{} { + return border.Value +} + +func (border *LexBorder) getExclude() bool { + return border.Exclude +} + +var lexPositiveInfBorder = &LexBorder{ + Inf: lexPositiveInf, +} + +var lexNegativeInfBorder = &LexBorder{ + Inf: lexNegativeInf, +} + +// ParseLexBorder creates LexBorder from redis arguments +func ParseLexBorder(s string) (Border, error) { + if s == "+" { + return lexPositiveInfBorder, nil + } + if s == "-" { + return lexNegativeInfBorder, nil + } + if s[0] == '(' { + return &LexBorder{ + Inf: 0, + Value: s[1:], + Exclude: true, + }, nil + } + + if s[0] == '[' { + return &LexBorder{ + Inf: 0, + Value: s[1:], + Exclude: false, + }, nil + } + + return nil, errors.New("ERR min or max not valid string range item") +} + +func (border *LexBorder) isIntersected(max Border) bool { + minValue := border.Value + maxValue := max.(*LexBorder).Value + return border.Inf == '+' || minValue > maxValue || (minValue == maxValue && (border.getExclude() || max.getExclude())) +} diff --git a/datastruct/sortedset/skiplist.go b/datastruct/sortedset/skiplist.go index 5a70d2c..d6ada0e 100644 --- a/datastruct/sortedset/skiplist.go +++ b/datastruct/sortedset/skiplist.go @@ -221,25 +221,25 @@ func (skiplist *skiplist) getByRank(rank int64) *node { return nil } -func (skiplist *skiplist) hasInRange(min *ScoreBorder, max *ScoreBorder) bool { - // min & max = empty - if min.Value > max.Value || (min.Value == max.Value && (min.Exclude || max.Exclude)) { +func (skiplist *skiplist) hasInRange(min Border, max Border) bool { + if min.isIntersected(max) { //是有交集的,则返回false return false } + // min > tail n := skiplist.tail - if n == nil || !min.less(n.Score) { + if n == nil || !min.less(&n.Element) { return false } // max < head n = skiplist.header.level[0].forward - if n == nil || !max.greater(n.Score) { + if n == nil || !max.greater(&n.Element) { return false } return true } -func (skiplist *skiplist) getFirstInScoreRange(min *ScoreBorder, max *ScoreBorder) *node { +func (skiplist *skiplist) getFirstInRange(min Border, max Border) *node { if !skiplist.hasInRange(min, max) { return nil } @@ -247,30 +247,30 @@ func (skiplist *skiplist) getFirstInScoreRange(min *ScoreBorder, max *ScoreBorde // scan from top level for level := skiplist.level - 1; level >= 0; level-- { // if forward is not in range than move forward - for n.level[level].forward != nil && !min.less(n.level[level].forward.Score) { + for n.level[level].forward != nil && !min.less(&n.level[level].forward.Element) { n = n.level[level].forward } } /* This is an inner range, so the next node cannot be NULL. */ n = n.level[0].forward - if !max.greater(n.Score) { + if !max.greater(&n.Element) { return nil } return n } -func (skiplist *skiplist) getLastInScoreRange(min *ScoreBorder, max *ScoreBorder) *node { +func (skiplist *skiplist) getLastInRange(min Border, max Border) *node { if !skiplist.hasInRange(min, max) { return nil } n := skiplist.header // scan from top level for level := skiplist.level - 1; level >= 0; level-- { - for n.level[level].forward != nil && max.greater(n.level[level].forward.Score) { + for n.level[level].forward != nil && max.greater(&n.level[level].forward.Element) { n = n.level[level].forward } } - if !min.less(n.Score) { + if !min.less(&n.Element) { return nil } return n @@ -279,14 +279,14 @@ func (skiplist *skiplist) getLastInScoreRange(min *ScoreBorder, max *ScoreBorder /* * return removed elements */ -func (skiplist *skiplist) RemoveRangeByScore(min *ScoreBorder, max *ScoreBorder, limit int) (removed []*Element) { +func (skiplist *skiplist) RemoveRange(min Border, max Border, limit int) (removed []*Element) { update := make([]*node, maxLevel) removed = make([]*Element, 0) // find backward nodes (of target range) or last node of each level node := skiplist.header for i := skiplist.level - 1; i >= 0; i-- { for node.level[i].forward != nil { - if min.less(node.level[i].forward.Score) { // already in range + if min.less(&node.level[i].forward.Element) { // already in range break } node = node.level[i].forward @@ -299,7 +299,7 @@ func (skiplist *skiplist) RemoveRangeByScore(min *ScoreBorder, max *ScoreBorder, // remove nodes in range for node != nil { - if !max.greater(node.Score) { // already out of range + if !max.greater(&node.Element) { // already out of range break } next := node.level[0].forward diff --git a/datastruct/sortedset/sortedset.go b/datastruct/sortedset/sortedset.go index 1a107ed..67f7942 100644 --- a/datastruct/sortedset/sortedset.go +++ b/datastruct/sortedset/sortedset.go @@ -18,7 +18,7 @@ func Make() *SortedSet { } } -// Add puts member into set, and returns whether has inserted new node +// Add puts member into set, and returns whether it has inserted new node func (sortedSet *SortedSet) Add(member string, score float64) bool { element, ok := sortedSet.dict[member] sortedSet.dict[member] = &Element{ @@ -76,8 +76,8 @@ func (sortedSet *SortedSet) GetRank(member string, desc bool) (rank int64) { return r } -// ForEach visits each member which rank within [start, stop), sort by ascending order, rank starts from 0 -func (sortedSet *SortedSet) ForEach(start int64, stop int64, desc bool, consumer func(element *Element) bool) { +// ForEachByRank visits each member which rank within [start, stop), sort by ascending order, rank starts from 0 +func (sortedSet *SortedSet) ForEachByRank(start int64, stop int64, desc bool, consumer func(element *Element) bool) { size := int64(sortedSet.Len()) if start < 0 || start >= size { panic("illegal start " + strconv.FormatInt(start, 10)) @@ -113,12 +113,12 @@ func (sortedSet *SortedSet) ForEach(start int64, stop int64, desc bool, consumer } } -// Range returns members which rank within [start, stop), sort by ascending order, rank starts from 0 -func (sortedSet *SortedSet) Range(start int64, stop int64, desc bool) []*Element { +// RangeByRank returns members which rank within [start, stop), sort by ascending order, rank starts from 0 +func (sortedSet *SortedSet) RangeByRank(start int64, stop int64, desc bool) []*Element { sliceSize := int(stop - start) slice := make([]*Element, sliceSize) i := 0 - sortedSet.ForEach(start, stop, desc, func(element *Element) bool { + sortedSet.ForEachByRank(start, stop, desc, func(element *Element) bool { slice[i] = element i++ return true @@ -126,17 +126,17 @@ func (sortedSet *SortedSet) Range(start int64, stop int64, desc bool) []*Element return slice } -// Count returns the number of members which score within the given border -func (sortedSet *SortedSet) Count(min *ScoreBorder, max *ScoreBorder) int64 { +// RangeCount returns the number of members which score or member within the given border +func (sortedSet *SortedSet) RangeCount(min Border, max Border) int64 { var i int64 = 0 // ascending order - sortedSet.ForEach(0, sortedSet.Len(), false, func(element *Element) bool { - gtMin := min.less(element.Score) // greater than min + sortedSet.ForEachByRank(0, sortedSet.Len(), false, func(element *Element) bool { + gtMin := min.less(element) // greater than min if !gtMin { // has not into range, continue foreach return true } - ltMax := max.greater(element.Score) // less than max + ltMax := max.greater(element) // less than max if !ltMax { // break through score border, break foreach return false @@ -148,14 +148,14 @@ func (sortedSet *SortedSet) Count(min *ScoreBorder, max *ScoreBorder) int64 { return i } -// ForEachByScore visits members which score within the given border -func (sortedSet *SortedSet) ForEachByScore(min *ScoreBorder, max *ScoreBorder, offset int64, limit int64, desc bool, consumer func(element *Element) bool) { +// ForEach visits members which score or member within the given border +func (sortedSet *SortedSet) ForEach(min Border, max Border, offset int64, limit int64, desc bool, consumer func(element *Element) bool) { // find start node var node *node if desc { - node = sortedSet.skiplist.getLastInScoreRange(min, max) + node = sortedSet.skiplist.getLastInRange(min, max) } else { - node = sortedSet.skiplist.getFirstInScoreRange(min, max) + node = sortedSet.skiplist.getFirstInRange(min, max) } for node != nil && offset > 0 { @@ -180,31 +180,31 @@ func (sortedSet *SortedSet) ForEachByScore(min *ScoreBorder, max *ScoreBorder, o if node == nil { break } - gtMin := min.less(node.Element.Score) // greater than min - ltMax := max.greater(node.Element.Score) + gtMin := min.less(&node.Element) // greater than min + ltMax := max.greater(&node.Element) if !gtMin || !ltMax { break // break through score border } } } -// RangeByScore returns members which score within the given border +// Range returns members which score or member within the given border // param limit: <0 means no limit -func (sortedSet *SortedSet) RangeByScore(min *ScoreBorder, max *ScoreBorder, offset int64, limit int64, desc bool) []*Element { +func (sortedSet *SortedSet) Range(min Border, max Border, offset int64, limit int64, desc bool) []*Element { if limit == 0 || offset < 0 { return make([]*Element, 0) } slice := make([]*Element, 0) - sortedSet.ForEachByScore(min, max, offset, limit, desc, func(element *Element) bool { + sortedSet.ForEach(min, max, offset, limit, desc, func(element *Element) bool { slice = append(slice, element) return true }) return slice } -// RemoveByScore removes members which score within the given border -func (sortedSet *SortedSet) RemoveByScore(min *ScoreBorder, max *ScoreBorder) int64 { - removed := sortedSet.skiplist.RemoveRangeByScore(min, max, 0) +// RemoveRange removes members which score or member within the given border +func (sortedSet *SortedSet) RemoveRange(min Border, max Border) int64 { + removed := sortedSet.skiplist.RemoveRange(min, max, 0) for _, element := range removed { delete(sortedSet.dict, element.Member) } @@ -212,7 +212,7 @@ func (sortedSet *SortedSet) RemoveByScore(min *ScoreBorder, max *ScoreBorder) in } func (sortedSet *SortedSet) PopMin(count int) []*Element { - first := sortedSet.skiplist.getFirstInScoreRange(negativeInfBorder, positiveInfBorder) + first := sortedSet.skiplist.getFirstInRange(scoreNegativeInfBorder, scorePositiveInfBorder) if first == nil { return nil } @@ -220,7 +220,7 @@ func (sortedSet *SortedSet) PopMin(count int) []*Element { Value: first.Score, Exclude: false, } - removed := sortedSet.skiplist.RemoveRangeByScore(border, positiveInfBorder, count) + removed := sortedSet.skiplist.RemoveRange(border, scorePositiveInfBorder, count) for _, element := range removed { delete(sortedSet.dict, element.Member) }