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  }