github.com/songzhibin97/gkit@v1.2.13/structure/zset/zset.go (about) 1 // Copyright 2021 ByteDance Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package zset provides a concurrent-safety sorted set, can be used as a local 16 // replacement of Redis' zset (https://redis.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/). 17 // 18 // The main different to other sets is, every value of set is associated with a score, 19 // that is used in order to take the sorted set ordered, from the smallest to the greatest score. 20 // 21 // The sorted set has O(log(N)) time complexity when doing Add(ZADD) and 22 // Remove(ZREM) operations and O(1) time complexity when doing Contains operations. 23 package zset 24 25 import ( 26 "sync" 27 ) 28 29 // Float64Node represents an element of Float64Set. 30 type Float64Node struct { 31 Value string 32 Score float64 33 } 34 35 // Float64Set is a sorted set implementation with string value and float64 score. 36 type Float64Set struct { 37 mu sync.RWMutex 38 dict map[string]float64 39 list *float64List 40 } 41 42 // NewFloat64 returns an empty string sorted set with int score. 43 // strings are sorted in ascending order. 44 func NewFloat64() *Float64Set { 45 return &Float64Set{ 46 dict: make(map[string]float64), 47 list: newFloat64List(), 48 } 49 } 50 51 // UnionFloat64 returns the union of given sorted sets, the resulting score of 52 // a value is the sum of its scores in the sorted sets where it exists. 53 // 54 // UnionFloat64 is the replacement of UNIONSTORE command of redis. 55 func UnionFloat64(zs ...*Float64Set) *Float64Set { 56 dest := NewFloat64() 57 for _, z := range zs { 58 for _, n := range z.Range(0, -1) { 59 dest.Add(n.Score, n.Value) 60 } 61 } 62 return dest 63 } 64 65 // InterFloat64 returns the intersection of given sorted sets, the resulting 66 // score of a value is the sum of its scores in the sorted sets where it exists. 67 // 68 // InterFloat64 is the replacement of INTERSTORE command of redis. 69 func InterFloat64(zs ...*Float64Set) *Float64Set { 70 dest := NewFloat64() 71 if len(zs) == 0 { 72 return dest 73 } 74 for _, n := range zs[0].Range(0, -1) { 75 ok := true 76 for _, z := range zs[1:] { 77 if !z.Contains(n.Value) { 78 ok = false 79 break 80 } 81 } 82 if ok { 83 dest.Add(n.Score, n.Value) 84 } 85 } 86 return dest 87 } 88 89 // Len returns the length of Float64Set. 90 // 91 // Len is the replacement of ZCARD command of redis. 92 func (z *Float64Set) Len() int { 93 z.mu.RLock() 94 defer z.mu.RUnlock() 95 96 return z.list.length 97 } 98 99 // Add adds a new value or update the score of an existing value. 100 // Returns true if the value is newly created. 101 // 102 // Add is the replacement of ZADD command of redis. 103 func (z *Float64Set) Add(score float64, value string) bool { 104 z.mu.Lock() 105 defer z.mu.Unlock() 106 107 oldScore, ok := z.dict[value] 108 if ok { 109 // Update score if need. 110 if score != oldScore { 111 _ = z.list.UpdateScore(oldScore, value, score) 112 z.dict[value] = score 113 } 114 return false 115 } 116 117 // Insert a new element. 118 z.list.Insert(score, value) 119 z.dict[value] = score 120 return true 121 } 122 123 // Remove removes a value from the sorted set. 124 // Returns score of the removed value and true if the node was found and deleted, 125 // otherwise returns (0.0, false). 126 // 127 // Remove is the replacement of ZREM command of redis. 128 func (z *Float64Set) Remove(value string) (float64, bool) { 129 z.mu.Lock() 130 defer z.mu.Unlock() 131 132 score, ok := z.dict[value] 133 if !ok { 134 return 0, false 135 } 136 delete(z.dict, value) 137 z.list.Delete(score, value) 138 return score, true 139 } 140 141 // IncrBy increments the score of value in the sorted set by incr. 142 // If value does not exist in the sorted set, it is added with incr as its score 143 // (as if its previous score was zero). 144 // 145 // IncrBy is the replacement of ZINCRBY command of redis. 146 func (z *Float64Set) IncrBy(incr float64, value string) (float64, bool) { 147 z.mu.Lock() 148 defer z.mu.Unlock() 149 150 oldScore, ok := z.dict[value] 151 if !ok { 152 // Insert a new element. 153 z.list.Insert(incr, value) 154 z.dict[value] = incr 155 return incr, false 156 } 157 // Update score. 158 newScore := oldScore + incr 159 _ = z.list.UpdateScore(oldScore, value, newScore) 160 z.dict[value] = newScore 161 return newScore, true 162 } 163 164 // Contains returns whether the value exists in sorted set. 165 func (z *Float64Set) Contains(value string) bool { 166 _, ok := z.Score(value) 167 return ok 168 } 169 170 // Score returns the score of the value in the sorted set. 171 // 172 // Score is the replacement of ZSCORE command of redis. 173 func (z *Float64Set) Score(value string) (float64, bool) { 174 z.mu.RLock() 175 defer z.mu.RUnlock() 176 177 score, ok := z.dict[value] 178 return score, ok 179 } 180 181 // Rank returns the rank of element in the sorted set, with the scores 182 // ordered from low to high. 183 // The rank (or index) is 0-based, which means that the member with the lowest 184 // score has rank 0. 185 // -1 is returned when value is not found. 186 // 187 // Rank is the replacement of ZRANK command of redis. 188 func (z *Float64Set) Rank(value string) int { 189 z.mu.RLock() 190 defer z.mu.RUnlock() 191 192 score, ok := z.dict[value] 193 if !ok { 194 return -1 195 } 196 // NOTE: list.Rank returns 1-based rank. 197 return z.list.Rank(score, value) - 1 198 } 199 200 // RevRank returns the rank of element in the sorted set, with the scores 201 // ordered from high to low. 202 // The rank (or index) is 0-based, which means that the member with the highest 203 // score has rank 0. 204 // -1 is returned when value is not found. 205 // 206 // RevRank is the replacement of ZREVRANK command of redis. 207 func (z *Float64Set) RevRank(value string) int { 208 z.mu.RLock() 209 defer z.mu.RUnlock() 210 211 score, ok := z.dict[value] 212 if !ok { 213 return -1 214 } 215 // NOTE: list.Rank returns 1-based rank. 216 return z.list.Rank(score, value) - 1 217 } 218 219 // Count returns the number of elements in the sorted set at element with a score 220 // between min and max (including elements with score equal to min or max). 221 // 222 // Count is the replacement of ZCOUNT command of redis. 223 func (z *Float64Set) Count(min, max float64) int { 224 return z.CountWithOpt(min, max, RangeOpt{}) 225 } 226 227 func (z *Float64Set) CountWithOpt(min, max float64, opt RangeOpt) int { 228 z.mu.RLock() 229 defer z.mu.RUnlock() 230 231 first := z.list.FirstInRange(min, max, opt) 232 if first == nil { 233 return 0 234 } 235 // Sub 1 for 1-based rank. 236 firstRank := z.list.Rank(first.score, first.value) - 1 237 last := z.list.LastInRange(min, max, opt) 238 if last == nil { 239 return z.list.length - firstRank 240 } 241 // Sub 1 for 1-based rank. 242 lastRank := z.list.Rank(last.score, last.value) - 1 243 return lastRank - firstRank + 1 244 } 245 246 // Range returns the specified inclusive range of elements in the sorted set by rank(index). 247 // Both start and stop are 0-based, they can also be negative numbers indicating 248 // offsets from the end of the sorted set, with -1 being the last element of the sorted set, 249 // and so on. 250 // 251 // The returned elements are ordered by score, from lowest to highest. 252 // Elements with the same score are ordered lexicographically. 253 // 254 // This function won't panic even when the given rank out of range. 255 // 256 // NOTE: Please always use z.Range(0, -1) for iterating the whole sorted set. 257 // z.Range(0, z.Len()-1) has 2 method calls, the sorted set may changes during 258 // the gap of calls. 259 // 260 // Range is the replacement of ZRANGE command of redis. 261 func (z *Float64Set) Range(start, stop int) []Float64Node { 262 z.mu.RLock() 263 defer z.mu.RUnlock() 264 265 // Convert negative rank to positive. 266 if start < 0 { 267 start = z.list.length + start 268 } 269 if stop < 0 { 270 stop = z.list.length + stop 271 } 272 273 var res []Float64Node 274 x := z.list.GetNodeByRank(start + 1) // 0-based rank -> 1-based rank 275 for x != nil && start <= stop { 276 start++ 277 res = append(res, Float64Node{ 278 Score: x.score, 279 Value: x.value, 280 }) 281 x = x.loadNext(0) 282 } 283 return res 284 } 285 286 // RangeByScore returns all the elements in the sorted set with a score 287 // between min and max (including elements with score equal to min or max). 288 // The elements are considered to be ordered from low to high scores. 289 // 290 // RangeByScore is the replacement of ZRANGEBYSCORE command of redis. 291 func (z *Float64Set) RangeByScore(min, max float64) []Float64Node { 292 return z.RangeByScoreWithOpt(min, max, RangeOpt{}) 293 } 294 295 func (z *Float64Set) RangeByScoreWithOpt(min, max float64, opt RangeOpt) []Float64Node { 296 z.mu.RLock() 297 defer z.mu.RUnlock() 298 299 var res []Float64Node 300 x := z.list.FirstInRange(min, max, opt) 301 for x != nil && (x.score < max || (!opt.ExcludeMax && x.score == max)) { 302 res = append(res, Float64Node{ 303 Score: x.score, 304 Value: x.value, 305 }) 306 x = x.loadNext(0) 307 } 308 return res 309 } 310 311 // RevRange returns the specified inclusive range of elements in the sorted set by rank(index). 312 // Both start and stop are 0-based, they can also be negative numbers indicating 313 // offsets from the end of the sorted set, with -1 being the first element of the sorted set, 314 // and so on. 315 // 316 // The returned elements are ordered by score, from highest to lowest. 317 // Elements with the same score are ordered in reverse lexicographical ordering. 318 // 319 // This function won't panic even when the given rank out of range. 320 // 321 // NOTE: Please always use z.RevRange(0, -1) for iterating the whole sorted set. 322 // z.RevRange(0, z.Len()-1) has 2 method calls, the sorted set may changes during 323 // the gap of calls. 324 // 325 // RevRange is the replacement of ZREVRANGE command of redis. 326 func (z *Float64Set) RevRange(start, stop int) []Float64Node { 327 z.mu.RLock() 328 defer z.mu.RUnlock() 329 330 // Convert negative rank to positive. 331 if start < 0 { 332 start = z.list.length + start 333 } 334 if stop < 0 { 335 stop = z.list.length + stop 336 } 337 338 var res []Float64Node 339 x := z.list.GetNodeByRank(z.list.length - start) // 0-based rank -> 1-based rank 340 for x != nil && start <= stop { 341 start++ 342 res = append(res, Float64Node{ 343 Score: x.score, 344 Value: x.value, 345 }) 346 x = x.prev 347 } 348 return res 349 } 350 351 // RevRangeByScore returns all the elements in the sorted set with a 352 // score between max and min (including elements with score equal to max or min). 353 // The elements are considered to be ordered from high to low scores. 354 // 355 // RevRangeByScore is the replacement of ZREVRANGEBYSCORE command of redis. 356 func (z *Float64Set) RevRangeByScore(max, min float64) []Float64Node { 357 return z.RevRangeByScoreWithOpt(max, min, RangeOpt{}) 358 } 359 360 func (z *Float64Set) RevRangeByScoreWithOpt(max, min float64, opt RangeOpt) []Float64Node { 361 z.mu.RLock() 362 defer z.mu.RUnlock() 363 364 var res []Float64Node 365 x := z.list.LastInRange(min, max, opt) 366 for x != nil && (x.score > min || (!opt.ExcludeMin && x.score == min)) { 367 res = append(res, Float64Node{ 368 Score: x.score, 369 Value: x.value, 370 }) 371 x = x.prev 372 } 373 return res 374 } 375 376 // RemoveRangeByRank removes all elements in the sorted set stored with rank 377 // between start and stop. 378 // Both start and stop are 0-based, they can also be negative numbers indicating 379 // offsets from the end of the sorted set, with -1 being the last element of the sorted set, 380 // and so on. 381 // 382 // RemoveRangeByRank is the replacement of ZREMRANGEBYRANK command of redis. 383 func (z *Float64Set) RemoveRangeByRank(start, stop int) []Float64Node { 384 z.mu.RLock() 385 defer z.mu.RUnlock() 386 387 // Convert negative rank to positive. 388 if start < 0 { 389 start = z.list.length + start 390 } 391 if stop < 0 { 392 stop = z.list.length + stop 393 } 394 395 return z.list.DeleteRangeByRank(start+1, stop+1, z.dict) // 0-based rank -> 1-based rank 396 } 397 398 // RemoveRangeByScore removes all elements in the sorted set stored with a score 399 // between min and max (including elements with score equal to min or max). 400 // 401 // RemoveRangeByScore is the replacement of ZREMRANGEBYSCORE command of redis. 402 func (z *Float64Set) RemoveRangeByScore(min, max float64) []Float64Node { 403 return z.RevRangeByScoreWithOpt(min, max, RangeOpt{}) 404 } 405 406 func (z *Float64Set) RemoveRangeByScoreWithOpt(min, max float64, opt RangeOpt) []Float64Node { 407 z.mu.RLock() 408 defer z.mu.RUnlock() 409 410 return z.list.DeleteRangeByScore(min, max, opt, z.dict) 411 }