├── go.mod ├── LICENSE ├── README.md ├── sortedsetnode.go ├── doc.go ├── sortedset_test.go └── sortedset.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wangjia184/sortedset 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jerry.Wang 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sorted Set in Golang 2 | 3 | Sorted Set is a data-struct inspired by the one from Redis. It allows fast access by key or score. 4 | 5 | | Property | Type | Description | 6 | |---|---|---| 7 | | `key` | `string` | The identifier of the node. It must be unique within the set. | 8 | | `value` | `interface {}` | value associated with this node | 9 | | `score` | `int64` | score is in order to take the sorted set ordered. It may be repeated. | 10 | 11 | Each node in the set is associated with a `key`. While `key`s are unique, `score`s may be repeated. 12 | Nodes are __taken in order instead of ordered afterwards__, from low score to high score. If scores are the same, the node is ordered by its key in lexicographic order. Each node in the set is associated with __rank__, which represents the position of the node in the sorted set. The __rank__ is 1-based, that is to say, rank 1 is the node with minimum score. 13 | 14 | Sorted Set is implemented basing on skip list and hash map internally. With sorted sets you can add, remove, or update nodes in a very fast way (in a time proportional to the logarithm of the number of nodes). You can also get ranges by score or by rank (position) in a very fast way. Accessing the middle of a sorted set is also very fast, so you can use Sorted Sets as a smart list of non repeating nodes where you can quickly access everything you need: nodes in order, fast existence test, fast access to nodes in the middle! 15 | 16 | A typical use case of sorted set is a leader board in a massive online game, where every time a new score is submitted you update it using `AddOrUpdate()`. You can easily take the top users using `GetByRankRange()`, you can also, given an user id, return its rank in the listing using `FindRank()`. Using `FindRank()` and `GetByRankRange()` together you can show users with a score similar to a given user. All very quickly. 17 | 18 | ## Documentation 19 | 20 | [https://godoc.org/github.com/wangjia184/sortedset](https://godoc.org/github.com/wangjia184/sortedset) -------------------------------------------------------------------------------- /sortedsetnode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Jerry.Wang 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 10 | // * Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package sortedset 26 | 27 | type SortedSetLevel struct { 28 | forward *SortedSetNode 29 | span int64 30 | } 31 | 32 | // Node in skip list 33 | type SortedSetNode struct { 34 | key string // unique key of this node 35 | Value interface{} // associated data 36 | score SCORE // score to determine the order of this node in the set 37 | backward *SortedSetNode 38 | level []SortedSetLevel 39 | } 40 | 41 | // Get the key of the node 42 | func (this *SortedSetNode) Key() string { 43 | return this.key 44 | } 45 | 46 | // Get the node of the node 47 | func (this *SortedSetNode) Score() SCORE { 48 | return this.score 49 | } 50 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Jerry.Wang 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 10 | // * Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | /* 26 | Package sortedset provides the data-struct allows fast access the element in set by key or by score(order). It is inspired by Sorted Set from Redis. 27 | 28 | Introduction 29 | 30 | Every node in the set is associated with these properties. 31 | 32 | type SortedSetNode struct { 33 | key string // unique key of this node 34 | Value interface{} // associated data 35 | score SCORE // int64 score to determine the order of this node in the set 36 | } 37 | 38 | Each node in the set is associated with a key. While keys are unique, scores may be repeated. Nodes are taken in order (from low score to high score) instead of ordered afterwards. If scores are the same, the node is ordered by its key in lexicographic order. Each node in the set also can be accessed by rank, which represents the position in the sorted set. 39 | 40 | Sorted Set is implemented basing on skip list and hash map internally. With sorted sets you can add, remove, or update nodes in a very fast way (in a time proportional to the logarithm of the number of nodes). You can also get ranges by score or by rank (position) in a very fast way. Accessing the middle of a sorted set is also very fast, so you can use Sorted Sets as a smart list of non repeating nodes where you can quickly access everything you need: nodes in order, fast existence test, fast access to nodes in the middle! 41 | 42 | Use Case 43 | 44 | A typical use case of sorted set is a leader board in a massive online game, where every time a new score is submitted you update it using AddOrUpdate() method. You can easily take the top users using GetByRankRange() method, you can also, given an user id, return its rank in the listing using FindRank() method. Using FindRank() and GetByRankRange() together you can show users with a score similar to a given user. All very quickly. 45 | 46 | 47 | Examples 48 | 49 | // create a new set 50 | set := sortedset.New() 51 | 52 | // fill in new node 53 | set.AddOrUpdate("a", 89, "Kelly") 54 | set.AddOrUpdate("b", 100, "Staley") 55 | set.AddOrUpdate("c", 100, "Jordon") 56 | set.AddOrUpdate("d", -321, "Park") 57 | set.AddOrUpdate("e", 101, "Albert") 58 | set.AddOrUpdate("f", 99, "Lyman") 59 | set.AddOrUpdate("g", 99, "Singleton") 60 | set.AddOrUpdate("h", 70, "Audrey") 61 | 62 | // update an existing node by key 63 | set.AddOrUpdate("e", 99, "ntrnrt") 64 | 65 | // get the node by key 66 | set.GetByKey("b") 67 | 68 | // remove node by key 69 | set.Remove("b") 70 | 71 | // get the number of nodes in this set 72 | set.GetCount() 73 | 74 | // find the rank(postion) in the set. 75 | set.FindRank("d") // return 1 here 76 | 77 | // get and remove the node with minimum score 78 | set.PopMin() 79 | 80 | // get the node with maximum score 81 | set.PeekMax() 82 | 83 | // get the node at rank 1 (the node with minimum score) 84 | set.GetByRank(1, false) 85 | 86 | // get & remove the node at rank -1 (the node with maximum score) 87 | set.GetByRank(-1, true) 88 | 89 | // get the node with the 2nd highest maximum score 90 | set.GetByRank(-2, false) 91 | 92 | // get nodes with in rank range [1, -1], that is all nodes actually 93 | set.GetByRankRange(1, -1, false) 94 | 95 | // get & remove the 2nd/3rd nodes in reserve order 96 | set.GetByRankRange(-2, -3, true) 97 | 98 | // get the nodes whose score are within the interval [60,100] 99 | set.GetByScoreRange(60, 100, nil) 100 | 101 | // get the nodes whose score are within the interval (60,100] 102 | set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{ 103 | ExcludeStart: true, 104 | }) 105 | 106 | // get the nodes whose score are within the interval [60,100) 107 | set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{ 108 | ExcludeEnd: true, 109 | }) 110 | 111 | // get the nodes whose score are within the interval [60,100] in reverse order 112 | set.GetByScoreRange(100, 60, nil) 113 | 114 | // get the top 2 nodes with lowest scores within the interval [60,100] 115 | set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{ 116 | Limit: 2, 117 | }) 118 | 119 | // get the top 2 nodes with highest scores within the interval [60,100] 120 | set.GetByScoreRange(100, 60, &GetByScoreRangeOptions{ 121 | Limit: 2, 122 | }) 123 | 124 | // get the top 2 nodes with highest scores within the interval (60,100) 125 | set.GetByScoreRange(100, 60, &GetByScoreRangeOptions{ 126 | Limit: 2, 127 | ExcludeStart: true, 128 | ExcludeEnd: true, 129 | }) 130 | */ 131 | package sortedset 132 | -------------------------------------------------------------------------------- /sortedset_test.go: -------------------------------------------------------------------------------- 1 | package sortedset 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func checkOrder(t *testing.T, nodes []*SortedSetNode, expectedOrder []string) { 8 | if len(expectedOrder) != len(nodes) { 9 | t.Errorf("nodes does not contain %d elements", len(expectedOrder)) 10 | } 11 | for i := 0; i < len(expectedOrder); i++ { 12 | if nodes[i].Key() != expectedOrder[i] { 13 | t.Errorf("nodes[%d] is %q, but the expected key is %q", i, nodes[i].Key(), expectedOrder[i]) 14 | } 15 | 16 | } 17 | } 18 | 19 | func checkIterByRankRange(t *testing.T, sortedset *SortedSet, start int, end int, expectedOrder []string) { 20 | var keys []string 21 | 22 | // check nil callback should do nothing 23 | sortedset.IterFuncByRankRange(start, end, nil) 24 | 25 | sortedset.IterFuncByRankRange(start, end, func(key string, _ interface{}) bool { 26 | keys = append(keys, key) 27 | return true 28 | }) 29 | if len(expectedOrder) != len(keys) { 30 | t.Errorf("keys does not contain %d elements", len(expectedOrder)) 31 | } 32 | for i := 0; i < len(expectedOrder); i++ { 33 | if keys[i] != expectedOrder[i] { 34 | t.Errorf("keys[%d] is %q, but the expected key is %q", i, keys[i], expectedOrder[i]) 35 | } 36 | } 37 | 38 | // check return early 39 | if len(expectedOrder) < 1 { 40 | return 41 | } 42 | // reset data 43 | keys = []string{} 44 | var i int 45 | sortedset.IterFuncByRankRange(start, end, func(key string, _ interface{}) bool { 46 | keys = append(keys, key) 47 | i++ 48 | // return early 49 | return i < len(expectedOrder)-1 50 | }) 51 | if len(expectedOrder)-1 != len(keys) { 52 | t.Errorf("keys does not contain %d elements", len(expectedOrder)-1) 53 | } 54 | for i := 0; i < len(expectedOrder)-1; i++ { 55 | if keys[i] != expectedOrder[i] { 56 | t.Errorf("keys[%d] is %q, but the expected key is %q", i, keys[i], expectedOrder[i]) 57 | } 58 | } 59 | 60 | } 61 | 62 | func checkRankRangeIterAndOrder(t *testing.T, sortedset *SortedSet, start int, end int, remove bool, expectedOrder []string) { 63 | checkIterByRankRange(t, sortedset, start, end, expectedOrder) 64 | nodes := sortedset.GetByRankRange(start, end, remove) 65 | checkOrder(t, nodes, expectedOrder) 66 | } 67 | 68 | func TestCase1(t *testing.T) { 69 | sortedset := New() 70 | 71 | sortedset.AddOrUpdate("a", 89, "Kelly") 72 | sortedset.AddOrUpdate("b", 100, "Staley") 73 | sortedset.AddOrUpdate("c", 100, "Jordon") 74 | sortedset.AddOrUpdate("d", -321, "Park") 75 | sortedset.AddOrUpdate("e", 101, "Albert") 76 | sortedset.AddOrUpdate("f", 99, "Lyman") 77 | sortedset.AddOrUpdate("g", 99, "Singleton") 78 | sortedset.AddOrUpdate("h", 70, "Audrey") 79 | 80 | sortedset.AddOrUpdate("e", 99, "ntrnrt") 81 | 82 | sortedset.Remove("b") 83 | 84 | node := sortedset.GetByRank(3, false) 85 | if node == nil || node.Key() != "a" { 86 | t.Error("GetByRank() does not return expected value `a`") 87 | } 88 | 89 | node = sortedset.GetByRank(-3, false) 90 | if node == nil || node.Key() != "f" { 91 | t.Error("GetByRank() does not return expected value `f`") 92 | } 93 | 94 | // get all nodes since the first one to last one 95 | checkRankRangeIterAndOrder(t, sortedset, 1, -1, false, []string{"d", "h", "a", "e", "f", "g", "c"}) 96 | 97 | // get & remove the 2nd/3rd nodes in reserve order 98 | checkRankRangeIterAndOrder(t, sortedset, -2, -3, true, []string{"g", "f"}) 99 | 100 | // get all nodes since the last one to first one 101 | checkRankRangeIterAndOrder(t, sortedset, -1, 1, false, []string{"c", "e", "a", "h", "d"}) 102 | 103 | } 104 | 105 | func TestCase2(t *testing.T) { 106 | 107 | // create a new set 108 | sortedset := New() 109 | 110 | // fill in new node 111 | sortedset.AddOrUpdate("a", 89, "Kelly") 112 | sortedset.AddOrUpdate("b", 100, "Staley") 113 | sortedset.AddOrUpdate("c", 100, "Jordon") 114 | sortedset.AddOrUpdate("d", -321, "Park") 115 | sortedset.AddOrUpdate("e", 101, "Albert") 116 | sortedset.AddOrUpdate("f", 99, "Lyman") 117 | sortedset.AddOrUpdate("g", 99, "Singleton") 118 | sortedset.AddOrUpdate("h", 70, "Audrey") 119 | 120 | // update an existing node 121 | sortedset.AddOrUpdate("e", 99, "ntrnrt") 122 | 123 | // remove node 124 | sortedset.Remove("b") 125 | 126 | nodes := sortedset.GetByScoreRange(-500, 500, nil) 127 | checkOrder(t, nodes, []string{"d", "h", "a", "e", "f", "g", "c"}) 128 | 129 | nodes = sortedset.GetByScoreRange(500, -500, nil) 130 | //t.Logf("%v", nodes) 131 | checkOrder(t, nodes, []string{"c", "g", "f", "e", "a", "h", "d"}) 132 | 133 | nodes = sortedset.GetByScoreRange(600, 500, nil) 134 | checkOrder(t, nodes, []string{}) 135 | 136 | nodes = sortedset.GetByScoreRange(500, 600, nil) 137 | checkOrder(t, nodes, []string{}) 138 | 139 | rank := sortedset.FindRank("f") 140 | if rank != 5 { 141 | t.Error("FindRank() does not return expected value `5`") 142 | } 143 | 144 | rank = sortedset.FindRank("d") 145 | if rank != 1 { 146 | t.Error("FindRank() does not return expected value `1`") 147 | } 148 | 149 | nodes = sortedset.GetByScoreRange(99, 100, nil) 150 | checkOrder(t, nodes, []string{"e", "f", "g", "c"}) 151 | 152 | nodes = sortedset.GetByScoreRange(90, 50, nil) 153 | checkOrder(t, nodes, []string{"a", "h"}) 154 | 155 | nodes = sortedset.GetByScoreRange(99, 100, &GetByScoreRangeOptions{ 156 | ExcludeStart: true, 157 | }) 158 | checkOrder(t, nodes, []string{"c"}) 159 | 160 | nodes = sortedset.GetByScoreRange(100, 99, &GetByScoreRangeOptions{ 161 | ExcludeStart: true, 162 | }) 163 | checkOrder(t, nodes, []string{"g", "f", "e"}) 164 | 165 | nodes = sortedset.GetByScoreRange(99, 100, &GetByScoreRangeOptions{ 166 | ExcludeEnd: true, 167 | }) 168 | checkOrder(t, nodes, []string{"e", "f", "g"}) 169 | 170 | nodes = sortedset.GetByScoreRange(100, 99, &GetByScoreRangeOptions{ 171 | ExcludeEnd: true, 172 | }) 173 | checkOrder(t, nodes, []string{"c"}) 174 | 175 | nodes = sortedset.GetByScoreRange(50, 100, &GetByScoreRangeOptions{ 176 | Limit: 2, 177 | }) 178 | checkOrder(t, nodes, []string{"h", "a"}) 179 | 180 | nodes = sortedset.GetByScoreRange(100, 50, &GetByScoreRangeOptions{ 181 | Limit: 2, 182 | }) 183 | checkOrder(t, nodes, []string{"c", "g"}) 184 | 185 | minNode := sortedset.PeekMin() 186 | if minNode == nil || minNode.Key() != "d" { 187 | t.Error("PeekMin() does not return expected value `d`") 188 | } 189 | 190 | minNode = sortedset.PopMin() 191 | if minNode == nil || minNode.Key() != "d" { 192 | t.Error("PopMin() does not return expected value `d`") 193 | } 194 | 195 | nodes = sortedset.GetByScoreRange(-500, 500, nil) 196 | checkOrder(t, nodes, []string{"h", "a", "e", "f", "g", "c"}) 197 | 198 | maxNode := sortedset.PeekMax() 199 | if maxNode == nil || maxNode.Key() != "c" { 200 | t.Error("PeekMax() does not return expected value `c`") 201 | } 202 | 203 | maxNode = sortedset.PopMax() 204 | if maxNode == nil || maxNode.Key() != "c" { 205 | t.Error("PopMax() does not return expected value `c`") 206 | } 207 | 208 | nodes = sortedset.GetByScoreRange(500, -500, nil) 209 | checkOrder(t, nodes, []string{"g", "f", "e", "a", "h"}) 210 | } 211 | -------------------------------------------------------------------------------- /sortedset.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Jerry.Wang 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, this 8 | // list of conditions and the following disclaimer. 9 | // 10 | // * Redistributions in binary form must reproduce the above copyright notice, 11 | // this list of conditions and the following disclaimer in the documentation 12 | // and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package sortedset 26 | 27 | import ( 28 | "math/rand" 29 | ) 30 | 31 | type SCORE int64 // the type of score 32 | 33 | const SKIPLIST_MAXLEVEL = 32 /* Should be enough for 2^32 elements */ 34 | const SKIPLIST_P = 0.25 /* Skiplist P = 1/4 */ 35 | 36 | type SortedSet struct { 37 | header *SortedSetNode 38 | tail *SortedSetNode 39 | length int64 40 | level int 41 | dict map[string]*SortedSetNode 42 | } 43 | 44 | func createNode(level int, score SCORE, key string, value interface{}) *SortedSetNode { 45 | node := SortedSetNode{ 46 | score: score, 47 | key: key, 48 | Value: value, 49 | level: make([]SortedSetLevel, level), 50 | } 51 | return &node 52 | } 53 | 54 | // Returns a random level for the new skiplist node we are going to create. 55 | // The return value of this function is between 1 and SKIPLIST_MAXLEVEL 56 | // (both inclusive), with a powerlaw-alike distribution where higher 57 | // levels are less likely to be returned. 58 | func randomLevel() int { 59 | level := 1 60 | for float64(rand.Int31()&0xFFFF) < float64(SKIPLIST_P*0xFFFF) { 61 | level += 1 62 | } 63 | if level < SKIPLIST_MAXLEVEL { 64 | return level 65 | } 66 | 67 | return SKIPLIST_MAXLEVEL 68 | } 69 | 70 | func (this *SortedSet) insertNode(score SCORE, key string, value interface{}) *SortedSetNode { 71 | var update [SKIPLIST_MAXLEVEL]*SortedSetNode 72 | var rank [SKIPLIST_MAXLEVEL]int64 73 | 74 | x := this.header 75 | for i := this.level - 1; i >= 0; i-- { 76 | /* store rank that is crossed to reach the insert position */ 77 | if this.level-1 == i { 78 | rank[i] = 0 79 | } else { 80 | rank[i] = rank[i+1] 81 | } 82 | 83 | for x.level[i].forward != nil && 84 | (x.level[i].forward.score < score || 85 | (x.level[i].forward.score == score && // score is the same but the key is different 86 | x.level[i].forward.key < key)) { 87 | rank[i] += x.level[i].span 88 | x = x.level[i].forward 89 | } 90 | update[i] = x 91 | } 92 | 93 | /* we assume the key is not already inside, since we allow duplicated 94 | * scores, and the re-insertion of score and redis object should never 95 | * happen since the caller of Insert() should test in the hash table 96 | * if the element is already inside or not. */ 97 | level := randomLevel() 98 | 99 | if level > this.level { // add a new level 100 | for i := this.level; i < level; i++ { 101 | rank[i] = 0 102 | update[i] = this.header 103 | update[i].level[i].span = this.length 104 | } 105 | this.level = level 106 | } 107 | 108 | x = createNode(level, score, key, value) 109 | for i := 0; i < level; i++ { 110 | x.level[i].forward = update[i].level[i].forward 111 | update[i].level[i].forward = x 112 | 113 | /* update span covered by update[i] as x is inserted here */ 114 | x.level[i].span = update[i].level[i].span - (rank[0] - rank[i]) 115 | update[i].level[i].span = (rank[0] - rank[i]) + 1 116 | } 117 | 118 | /* increment span for untouched levels */ 119 | for i := level; i < this.level; i++ { 120 | update[i].level[i].span++ 121 | } 122 | 123 | if update[0] == this.header { 124 | x.backward = nil 125 | } else { 126 | x.backward = update[0] 127 | } 128 | if x.level[0].forward != nil { 129 | x.level[0].forward.backward = x 130 | } else { 131 | this.tail = x 132 | } 133 | this.length++ 134 | return x 135 | } 136 | 137 | /* Internal function used by delete, DeleteByScore and DeleteByRank */ 138 | func (this *SortedSet) deleteNode(x *SortedSetNode, update [SKIPLIST_MAXLEVEL]*SortedSetNode) { 139 | for i := 0; i < this.level; i++ { 140 | if update[i].level[i].forward == x { 141 | update[i].level[i].span += x.level[i].span - 1 142 | update[i].level[i].forward = x.level[i].forward 143 | } else { 144 | update[i].level[i].span -= 1 145 | } 146 | } 147 | if x.level[0].forward != nil { 148 | x.level[0].forward.backward = x.backward 149 | } else { 150 | this.tail = x.backward 151 | } 152 | for this.level > 1 && this.header.level[this.level-1].forward == nil { 153 | this.level-- 154 | } 155 | this.length-- 156 | delete(this.dict, x.key) 157 | } 158 | 159 | /* Delete an element with matching score/key from the skiplist. */ 160 | func (this *SortedSet) delete(score SCORE, key string) bool { 161 | var update [SKIPLIST_MAXLEVEL]*SortedSetNode 162 | 163 | x := this.header 164 | for i := this.level - 1; i >= 0; i-- { 165 | for x.level[i].forward != nil && 166 | (x.level[i].forward.score < score || 167 | (x.level[i].forward.score == score && 168 | x.level[i].forward.key < key)) { 169 | x = x.level[i].forward 170 | } 171 | update[i] = x 172 | } 173 | /* We may have multiple elements with the same score, what we need 174 | * is to find the element with both the right score and object. */ 175 | x = x.level[0].forward 176 | if x != nil && score == x.score && x.key == key { 177 | this.deleteNode(x, update) 178 | // free x 179 | return true 180 | } 181 | return false /* not found */ 182 | } 183 | 184 | // Create a new SortedSet 185 | func New() *SortedSet { 186 | sortedSet := SortedSet{ 187 | level: 1, 188 | dict: make(map[string]*SortedSetNode), 189 | } 190 | sortedSet.header = createNode(SKIPLIST_MAXLEVEL, 0, "", nil) 191 | return &sortedSet 192 | } 193 | 194 | // Get the number of elements 195 | func (this *SortedSet) GetCount() int { 196 | return int(this.length) 197 | } 198 | 199 | // get the element with minimum score, nil if the set is empty 200 | // 201 | // Time complexity of this method is : O(log(N)) 202 | func (this *SortedSet) PeekMin() *SortedSetNode { 203 | return this.header.level[0].forward 204 | } 205 | 206 | // get and remove the element with minimal score, nil if the set is empty 207 | // 208 | // // Time complexity of this method is : O(log(N)) 209 | func (this *SortedSet) PopMin() *SortedSetNode { 210 | x := this.header.level[0].forward 211 | if x != nil { 212 | this.Remove(x.key) 213 | } 214 | return x 215 | } 216 | 217 | // get the element with maximum score, nil if the set is empty 218 | // Time Complexity : O(1) 219 | func (this *SortedSet) PeekMax() *SortedSetNode { 220 | return this.tail 221 | } 222 | 223 | // get and remove the element with maximum score, nil if the set is empty 224 | // 225 | // Time complexity of this method is : O(log(N)) 226 | func (this *SortedSet) PopMax() *SortedSetNode { 227 | x := this.tail 228 | if x != nil { 229 | this.Remove(x.key) 230 | } 231 | return x 232 | } 233 | 234 | // Add an element into the sorted set with specific key / value / score. 235 | // if the element is added, this method returns true; otherwise false means updated 236 | // 237 | // Time complexity of this method is : O(log(N)) 238 | func (this *SortedSet) AddOrUpdate(key string, score SCORE, value interface{}) bool { 239 | var newNode *SortedSetNode = nil 240 | 241 | found := this.dict[key] 242 | if found != nil { 243 | // score does not change, only update value 244 | if found.score == score { 245 | found.Value = value 246 | } else { // score changes, delete and re-insert 247 | this.delete(found.score, found.key) 248 | newNode = this.insertNode(score, key, value) 249 | } 250 | } else { 251 | newNode = this.insertNode(score, key, value) 252 | } 253 | 254 | if newNode != nil { 255 | this.dict[key] = newNode 256 | } 257 | return found == nil 258 | } 259 | 260 | // Delete element specified by key 261 | // 262 | // Time complexity of this method is : O(log(N)) 263 | func (this *SortedSet) Remove(key string) *SortedSetNode { 264 | found := this.dict[key] 265 | if found != nil { 266 | this.delete(found.score, found.key) 267 | return found 268 | } 269 | return nil 270 | } 271 | 272 | type GetByScoreRangeOptions struct { 273 | Limit int // limit the max nodes to return 274 | ExcludeStart bool // exclude start value, so it search in interval (start, end] or (start, end) 275 | ExcludeEnd bool // exclude end value, so it search in interval [start, end) or (start, end) 276 | } 277 | 278 | // Get the nodes whose score within the specific range 279 | // 280 | // If options is nil, it searchs in interval [start, end] without any limit by default 281 | // 282 | // Time complexity of this method is : O(log(N)) 283 | func (this *SortedSet) GetByScoreRange(start SCORE, end SCORE, options *GetByScoreRangeOptions) []*SortedSetNode { 284 | 285 | // prepare parameters 286 | var limit int = int((^uint(0)) >> 1) 287 | if options != nil && options.Limit > 0 { 288 | limit = options.Limit 289 | } 290 | 291 | excludeStart := options != nil && options.ExcludeStart 292 | excludeEnd := options != nil && options.ExcludeEnd 293 | reverse := start > end 294 | if reverse { 295 | start, end = end, start 296 | excludeStart, excludeEnd = excludeEnd, excludeStart 297 | } 298 | 299 | ////////////////////////// 300 | var nodes []*SortedSetNode 301 | 302 | //determine if out of range 303 | if this.length == 0 { 304 | return nodes 305 | } 306 | ////////////////////////// 307 | 308 | if reverse { // search from end to start 309 | x := this.header 310 | 311 | if excludeEnd { 312 | for i := this.level - 1; i >= 0; i-- { 313 | for x.level[i].forward != nil && 314 | x.level[i].forward.score < end { 315 | x = x.level[i].forward 316 | } 317 | } 318 | } else { 319 | for i := this.level - 1; i >= 0; i-- { 320 | for x.level[i].forward != nil && 321 | x.level[i].forward.score <= end { 322 | x = x.level[i].forward 323 | } 324 | } 325 | } 326 | 327 | for x != nil && limit > 0 { 328 | if excludeStart { 329 | if x.score <= start { 330 | break 331 | } 332 | } else { 333 | if x.score < start { 334 | break 335 | } 336 | } 337 | 338 | next := x.backward 339 | 340 | nodes = append(nodes, x) 341 | limit-- 342 | 343 | x = next 344 | } 345 | } else { 346 | // search from start to end 347 | x := this.header 348 | if excludeStart { 349 | for i := this.level - 1; i >= 0; i-- { 350 | for x.level[i].forward != nil && 351 | x.level[i].forward.score <= start { 352 | x = x.level[i].forward 353 | } 354 | } 355 | } else { 356 | for i := this.level - 1; i >= 0; i-- { 357 | for x.level[i].forward != nil && 358 | x.level[i].forward.score < start { 359 | x = x.level[i].forward 360 | } 361 | } 362 | } 363 | 364 | /* Current node is the last with score < or <= start. */ 365 | x = x.level[0].forward 366 | 367 | for x != nil && limit > 0 { 368 | if excludeEnd { 369 | if x.score >= end { 370 | break 371 | } 372 | } else { 373 | if x.score > end { 374 | break 375 | } 376 | } 377 | 378 | next := x.level[0].forward 379 | 380 | nodes = append(nodes, x) 381 | limit-- 382 | 383 | x = next 384 | } 385 | } 386 | 387 | return nodes 388 | } 389 | 390 | // sanitizeIndexes return start, end, and reverse flag 391 | func (this *SortedSet) sanitizeIndexes(start int, end int) (int, int, bool) { 392 | if start < 0 { 393 | start = int(this.length) + start + 1 394 | } 395 | if end < 0 { 396 | end = int(this.length) + end + 1 397 | } 398 | if start <= 0 { 399 | start = 1 400 | } 401 | if end <= 0 { 402 | end = 1 403 | } 404 | 405 | reverse := start > end 406 | if reverse { // swap start and end 407 | start, end = end, start 408 | } 409 | return start, end, reverse 410 | } 411 | 412 | func (this *SortedSet) findNodeByRank(start int, remove bool) (traversed int, x *SortedSetNode, update [SKIPLIST_MAXLEVEL]*SortedSetNode) { 413 | x = this.header 414 | for i := this.level - 1; i >= 0; i-- { 415 | for x.level[i].forward != nil && 416 | traversed+int(x.level[i].span) < start { 417 | traversed += int(x.level[i].span) 418 | x = x.level[i].forward 419 | } 420 | if remove { 421 | update[i] = x 422 | } else { 423 | if traversed+1 == start { 424 | break 425 | } 426 | } 427 | } 428 | return 429 | } 430 | 431 | // Get nodes within specific rank range [start, end] 432 | // Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node; 433 | // 434 | // If start is greater than end, the returned array is in reserved order 435 | // If remove is true, the returned nodes are removed 436 | // 437 | // Time complexity of this method is : O(log(N)) 438 | func (this *SortedSet) GetByRankRange(start int, end int, remove bool) []*SortedSetNode { 439 | start, end, reverse := this.sanitizeIndexes(start, end) 440 | 441 | var nodes []*SortedSetNode 442 | 443 | traversed, x, update := this.findNodeByRank(start, remove) 444 | 445 | traversed++ 446 | x = x.level[0].forward 447 | for x != nil && traversed <= end { 448 | next := x.level[0].forward 449 | 450 | nodes = append(nodes, x) 451 | 452 | if remove { 453 | this.deleteNode(x, update) 454 | } 455 | 456 | traversed++ 457 | x = next 458 | } 459 | 460 | if reverse { 461 | for i, j := 0, len(nodes)-1; i < j; i, j = i+1, j-1 { 462 | nodes[i], nodes[j] = nodes[j], nodes[i] 463 | } 464 | } 465 | return nodes 466 | } 467 | 468 | // Get node by rank. 469 | // Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node; 470 | // 471 | // If remove is true, the returned nodes are removed 472 | // If node is not found at specific rank, nil is returned 473 | // 474 | // Time complexity of this method is : O(log(N)) 475 | func (this *SortedSet) GetByRank(rank int, remove bool) *SortedSetNode { 476 | nodes := this.GetByRankRange(rank, rank, remove) 477 | if len(nodes) == 1 { 478 | return nodes[0] 479 | } 480 | return nil 481 | } 482 | 483 | // Get node by key 484 | // 485 | // If node is not found, nil is returned 486 | // Time complexity : O(1) 487 | func (this *SortedSet) GetByKey(key string) *SortedSetNode { 488 | return this.dict[key] 489 | } 490 | 491 | // Find the rank of the node specified by key 492 | // Note that the rank is 1-based integer. Rank 1 means the first node 493 | // 494 | // If the node is not found, 0 is returned. Otherwise rank(> 0) is returned 495 | // 496 | // Time complexity of this method is : O(log(N)) 497 | func (this *SortedSet) FindRank(key string) int { 498 | var rank int = 0 499 | node := this.dict[key] 500 | if node != nil { 501 | x := this.header 502 | for i := this.level - 1; i >= 0; i-- { 503 | for x.level[i].forward != nil && 504 | (x.level[i].forward.score < node.score || 505 | (x.level[i].forward.score == node.score && 506 | x.level[i].forward.key <= node.key)) { 507 | rank += int(x.level[i].span) 508 | x = x.level[i].forward 509 | } 510 | 511 | if x.key == key { 512 | return rank 513 | } 514 | } 515 | } 516 | return 0 517 | } 518 | 519 | // IterFuncByRankRange apply fn to node within specific rank range [start, end] 520 | // or until fn return false 521 | // 522 | // Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node; 523 | // If start is greater than end, apply fn in reserved order 524 | // If fn is nil, this function return without doing anything 525 | func (this *SortedSet) IterFuncByRankRange(start int, end int, fn func(key string, value interface{}) bool) { 526 | if fn == nil { 527 | return 528 | } 529 | 530 | start, end, reverse := this.sanitizeIndexes(start, end) 531 | traversed, x, _ := this.findNodeByRank(start, false) 532 | var nodes []*SortedSetNode 533 | 534 | x = x.level[0].forward 535 | for x != nil && traversed < end { 536 | next := x.level[0].forward 537 | 538 | if reverse { 539 | nodes = append(nodes, x) 540 | } else if !fn(x.key, x.Value) { 541 | return 542 | } 543 | 544 | traversed++ 545 | x = next 546 | } 547 | 548 | if reverse { 549 | for i := len(nodes) - 1; i >= 0; i-- { 550 | if !fn(nodes[i].key, nodes[i].Value) { 551 | return 552 | } 553 | } 554 | } 555 | } 556 | --------------------------------------------------------------------------------