github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/collection/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 } else { 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 165 // Contains returns whether the value exists in sorted set. 166 func (z *Float64Set) Contains(value string) bool { 167 _, ok := z.Score(value) 168 return ok 169 } 170 171 // Score returns the score of the value in the sorted set. 172 // 173 // Score is the replacement of ZSCORE command of redis. 174 func (z *Float64Set) Score(value string) (float64, bool) { 175 z.mu.RLock() 176 defer z.mu.RUnlock() 177 178 score, ok := z.dict[value] 179 return score, ok 180 } 181 182 // Rank returns the rank of element in the sorted set, with the scores 183 // ordered from low to high. 184 // The rank (or index) is 0-based, which means that the member with the lowest 185 // score has rank 0. 186 // -1 is returned when value is not found. 187 // 188 // Rank is the replacement of ZRANK command of redis. 189 func (z *Float64Set) Rank(value string) int { 190 z.mu.RLock() 191 defer z.mu.RUnlock() 192 193 score, ok := z.dict[value] 194 if !ok { 195 return -1 196 } 197 // NOTE: list.Rank returns 1-based rank. 198 return z.list.Rank(score, value) - 1 199 } 200 201 // RevRank returns the rank of element in the sorted set, with the scores 202 // ordered from high to low. 203 // The rank (or index) is 0-based, which means that the member with the highest 204 // score has rank 0. 205 // -1 is returned when value is not found. 206 // 207 // RevRank is the replacement of ZREVRANK command of redis. 208 func (z *Float64Set) RevRank(value string) int { 209 z.mu.RLock() 210 defer z.mu.RUnlock() 211 212 score, ok := z.dict[value] 213 if !ok { 214 return -1 215 } 216 // NOTE: list.Rank returns 1-based rank. 217 return z.list.Rank(score, value) - 1 218 } 219 220 // Count returns the number of elements in the sorted set at element with a score 221 // between min and max (including elements with score equal to min or max). 222 // 223 // Count is the replacement of ZCOUNT command of redis. 224 func (z *Float64Set) Count(min, max float64) int { 225 return z.CountWithOpt(min, max, RangeOpt{}) 226 } 227 228 func (z *Float64Set) CountWithOpt(min, max float64, opt RangeOpt) int { 229 z.mu.RLock() 230 defer z.mu.RUnlock() 231 232 first := z.list.FirstInRange(min, max, opt) 233 if first == nil { 234 return 0 235 } 236 // Sub 1 for 1-based rank. 237 firstRank := z.list.Rank(first.score, first.value) - 1 238 last := z.list.LastInRange(min, max, opt) 239 if last == nil { 240 return z.list.length - firstRank 241 } 242 // Sub 1 for 1-based rank. 243 lastRank := z.list.Rank(last.score, last.value) - 1 244 return lastRank - firstRank + 1 245 } 246 247 // Range returns the specified inclusive range of elements in the sorted set by rank(index). 248 // Both start and stop are 0-based, they can also be negative numbers indicating 249 // offsets from the end of the sorted set, with -1 being the last element of the sorted set, 250 // and so on. 251 // 252 // The returned elements are ordered by score, from lowest to highest. 253 // Elements with the same score are ordered lexicographically. 254 // 255 // This function won't panic even when the given rank out of range. 256 // 257 // NOTE: Please always use z.Range(0, -1) for iterating the whole sorted set. 258 // z.Range(0, z.Len()-1) has 2 method calls, the sorted set may changes during 259 // the gap of calls. 260 // 261 // Range is the replacement of ZRANGE command of redis. 262 func (z *Float64Set) Range(start, stop int) []Float64Node { 263 z.mu.RLock() 264 defer z.mu.RUnlock() 265 266 // Convert negative rank to positive. 267 if start < 0 { 268 start = z.list.length + start 269 } 270 if stop < 0 { 271 stop = z.list.length + stop 272 } 273 274 var res []Float64Node 275 x := z.list.GetNodeByRank(start + 1) // 0-based rank -> 1-based rank 276 for x != nil && start <= stop { 277 start++ 278 res = append(res, Float64Node{ 279 Score: x.score, 280 Value: x.value, 281 }) 282 x = x.loadNext(0) 283 } 284 return res 285 } 286 287 // RangeByScore returns all the elements in the sorted set with a score 288 // between min and max (including elements with score equal to min or max). 289 // The elements are considered to be ordered from low to high scores. 290 // 291 // RangeByScore is the replacement of ZRANGEBYSCORE command of redis. 292 func (z *Float64Set) RangeByScore(min, max float64) []Float64Node { 293 return z.RangeByScoreWithOpt(min, max, RangeOpt{}) 294 } 295 296 func (z *Float64Set) RangeByScoreWithOpt(min, max float64, opt RangeOpt) []Float64Node { 297 z.mu.RLock() 298 defer z.mu.RUnlock() 299 300 var res []Float64Node 301 x := z.list.FirstInRange(min, max, opt) 302 for x != nil && (x.score < max || (!opt.ExcludeMax && x.score == max)) { 303 res = append(res, Float64Node{ 304 Score: x.score, 305 Value: x.value, 306 }) 307 x = x.loadNext(0) 308 } 309 return res 310 } 311 312 // RevRange returns the specified inclusive range of elements in the sorted set by rank(index). 313 // Both start and stop are 0-based, they can also be negative numbers indicating 314 // offsets from the end of the sorted set, with -1 being the first element of the sorted set, 315 // and so on. 316 // 317 // The returned elements are ordered by score, from highest to lowest. 318 // Elements with the same score are ordered in reverse lexicographical ordering. 319 // 320 // This function won't panic even when the given rank out of range. 321 // 322 // NOTE: Please always use z.RevRange(0, -1) for iterating the whole sorted set. 323 // z.RevRange(0, z.Len()-1) has 2 method calls, the sorted set may changes during 324 // the gap of calls. 325 // 326 // RevRange is the replacement of ZREVRANGE command of redis. 327 func (z *Float64Set) RevRange(start, stop int) []Float64Node { 328 z.mu.RLock() 329 defer z.mu.RUnlock() 330 331 // Convert negative rank to positive. 332 if start < 0 { 333 start = z.list.length + start 334 } 335 if stop < 0 { 336 stop = z.list.length + stop 337 } 338 339 var res []Float64Node 340 x := z.list.GetNodeByRank(z.list.length - start) // 0-based rank -> 1-based rank 341 for x != nil && start <= stop { 342 start++ 343 res = append(res, Float64Node{ 344 Score: x.score, 345 Value: x.value, 346 }) 347 x = x.prev 348 } 349 return res 350 } 351 352 // RevRangeByScore returns all the elements in the sorted set with a 353 // score between max and min (including elements with score equal to max or min). 354 // The elements are considered to be ordered from high to low scores. 355 // 356 // RevRangeByScore is the replacement of ZREVRANGEBYSCORE command of redis. 357 func (z *Float64Set) RevRangeByScore(max, min float64) []Float64Node { 358 return z.RevRangeByScoreWithOpt(max, min, RangeOpt{}) 359 } 360 361 func (z *Float64Set) RevRangeByScoreWithOpt(max, min float64, opt RangeOpt) []Float64Node { 362 z.mu.RLock() 363 defer z.mu.RUnlock() 364 365 var res []Float64Node 366 x := z.list.LastInRange(min, max, opt) 367 for x != nil && (x.score > min || (!opt.ExcludeMin && x.score == min)) { 368 res = append(res, Float64Node{ 369 Score: x.score, 370 Value: x.value, 371 }) 372 x = x.prev 373 } 374 return res 375 } 376 377 // RemoveRangeByRank removes all elements in the sorted set stored with rank 378 // between start and stop. 379 // Both start and stop are 0-based, they can also be negative numbers indicating 380 // offsets from the end of the sorted set, with -1 being the last element of the sorted set, 381 // and so on. 382 // 383 // RemoveRangeByRank is the replacement of ZREMRANGEBYRANK command of redis. 384 func (z *Float64Set) RemoveRangeByRank(start, stop int) []Float64Node { 385 z.mu.RLock() 386 defer z.mu.RUnlock() 387 388 // Convert negative rank to positive. 389 if start < 0 { 390 start = z.list.length + start 391 } 392 if stop < 0 { 393 stop = z.list.length + stop 394 } 395 396 return z.list.DeleteRangeByRank(start+1, stop+1, z.dict) // 0-based rank -> 1-based rank 397 } 398 399 // RemoveRangeByScore removes all elements in the sorted set stored with a score 400 // between min and max (including elements with score equal to min or max). 401 // 402 // RemoveRangeByScore is the replacement of ZREMRANGEBYSCORE command of redis. 403 func (z *Float64Set) RemoveRangeByScore(min, max float64) []Float64Node { 404 return z.RemoveRangeByScoreWithOpt(min, max, RangeOpt{}) 405 } 406 407 func (z *Float64Set) RemoveRangeByScoreWithOpt(min, max float64, opt RangeOpt) []Float64Node { 408 z.mu.RLock() 409 defer z.mu.RUnlock() 410 411 return z.list.DeleteRangeByScore(min, max, opt, z.dict) 412 }