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  }