sortedset

package module
v0.0.0-...-3ca298b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: BSD-2-Clause Imports: 3 Imported by: 27

README

Sorted Set in Go

A Redis-inspired sorted set backed by a skip list. Nodes are taken in order from low score to high score; ties are ordered by key. Access by key is via a hash index, so membership tests are O(1) and score/rank operations are O(log N).

API

SortedSet[K constraints.Ordered, SCORE constraints.Ordered, V any]

Method Description
AddOrUpdate(key K, score SCORE, value V) bool Insert or update; true when the key was new
Remove(key K) *SortedSetNode[K, SCORE, V] Delete by key
GetByKey(key K) *SortedSetNode[...] Look up a node by key
GetCount() int Number of nodes
PeekMin() / PopMin() / PeekMax() / PopMax() Extremes, with or without removal
FindRank(key K) int 1-based rank of a key (0 when absent)
GetRangeByScore(start, end SCORE, options *GetRangeByScoreOptions) Nodes whose score is in range
GetRangeByRank(start, end int, remove bool) Nodes by 1-based rank range
GetByRank(rank int, remove bool) Single node by rank
IterFuncRangeByRank(start, end int, fn func(K, V) bool) Iterate a rank range
Has(key K) bool Concurrent-safe membership test

A node exposes Key() K, Score() SCORE, and the public Value V field.

Concurrency contract

The set is not safe for concurrent use as a whole. Only Has may be called from a goroutine other than the one owning the set: it reads the sync.Map-backed index only, so a handler goroutine can poll membership while the owner mutates. Every other method takes no lock and must be called from a single goroutine.

Requirements

Go 1.26+ (uses golang.org/x/exp/constraints).

Usage

import sortedset "github.com/wangjia184/sortedset"

set := sortedset.New[string, int64, string]()
set.AddOrUpdate("a", 89, "Kelly")
set.AddOrUpdate("b", 100, "Staley")

min := set.PeekMin() // Key() == "a"
rank := set.FindRank("b") // 2
all := set.GetRangeByRank(1, -1, false) // ascending
if set.Has("a") { // safe from any goroutine
    // ...
}

Timing

All operations are O(log N), except GetByKey/Has/Remove lookups which are O(1). GetRangeByScore is O(log N) to locate the start, then O(m) for m returned nodes.

Documentation

Overview

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.

Introduction

Every node in the set is associated with these properties.

type SortedSetNode struct {
    key      string      // unique key of this node
    Value    interface{} // associated data
    score    SCORE       // int64 score to determine the order of this node in the set
}

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.

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!

Use Case

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.

Examples

// create a new set
set := sortedset.New()

// fill in new node
set.AddOrUpdate("a", 89, "Kelly")
set.AddOrUpdate("b", 100, "Staley")
set.AddOrUpdate("c", 100, "Jordon")
set.AddOrUpdate("d", -321, "Park")
set.AddOrUpdate("e", 101, "Albert")
set.AddOrUpdate("f", 99, "Lyman")
set.AddOrUpdate("g", 99, "Singleton")
set.AddOrUpdate("h", 70, "Audrey")

// update an existing node by key
set.AddOrUpdate("e", 99, "ntrnrt")

// get the node by key
set.GetByKey("b")

// remove node by key
set.Remove("b")

// get the number of nodes in this set
set.GetCount()

// find the rank(postion) in the set.
set.FindRank("d") // return 1 here

// get and remove the node with minimum score
set.PopMin()

// get the node with maximum score
set.PeekMax()

// get the node at rank 1 (the node with minimum score)
set.GetByRank(1, false)

// get & remove the node at rank -1 (the node with maximum score)
set.GetByRank(-1, true)

// get the node with the 2nd highest maximum score
set.GetByRank(-2, false)

// get nodes with in rank range [1, -1],  that is all nodes actually
set.GetByRankRange(1, -1, false)

// get & remove the 2nd/3rd nodes in reserve order
set.GetByRankRange(-2, -3, true)

// get the nodes whose score are within the interval [60,100]
set.GetByScoreRange(60, 100, nil)

// get the nodes whose score are within the interval (60,100]
set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{
    ExcludeStart: true,
})

// get the nodes whose score are within the interval [60,100)
set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{
    ExcludeEnd: true,
})

// get the nodes whose score are within the interval [60,100] in reverse order
set.GetByScoreRange(100, 60, nil)

// get the top 2 nodes with lowest scores within the interval [60,100]
set.GetByScoreRange(60, 100, &GetByScoreRangeOptions{
    Limit: 2,
})

// get the top 2 nodes with highest scores within the interval [60,100]
set.GetByScoreRange(100, 60, &GetByScoreRangeOptions{
    Limit: 2,
})

// get the top 2 nodes with highest scores within the interval (60,100)
set.GetByScoreRange(100, 60, &GetByScoreRangeOptions{
    Limit: 2,
    ExcludeStart: true,
    ExcludeEnd: true,
})

Index

Constants

View Source
const SKIPLIST_MAXLEVEL = 32 /* Should be enough for 2^32 elements */
View Source
const SKIPLIST_P = 0.25 /* Skiplist P = 1/4 */

Variables

This section is empty.

Functions

This section is empty.

Types

type GetRangeByScoreOptions

type GetRangeByScoreOptions struct {
	Limit        int  // limit the max nodes to return
	ExcludeStart bool // exclude start value, so it search in interval (start, end] or (start, end)
	ExcludeEnd   bool // exclude end value, so it search in interval [start, end) or (start, end)
}

type SortedSet

type SortedSet[K constraints.Ordered, SCORE constraints.Ordered, V any] struct {
	// contains filtered or unexported fields
}

SortedSet is a skip list keyed by K and ordered by SCORE.

Concurrency contract: the struct is NOT safe for concurrent use as a whole. Only Has -- a sync.Map membership check -- may be called from a goroutine other than the one owning the set. Every other method (AddOrUpdate, Remove, GetRangeByScore, GetRangeByRank, FindRank, Peek/Pop*, GetCount, ...) is not thread-safe and takes no lock: the caller must invoke them from a single goroutine.

func New

func New[K constraints.Ordered, SCORE constraints.Ordered, V any]() *SortedSet[K, SCORE, V]

Create a new SortedSet

func (*SortedSet[K, SCORE, V]) AddOrUpdate

func (this *SortedSet[K, SCORE, V]) AddOrUpdate(key K, score SCORE, value V) bool

Add an element into the sorted set with specific key / value / score. if the element is added, this method returns true; otherwise false means updated

Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) FindRank

func (this *SortedSet[K, SCORE, V]) FindRank(key K) int

Find the rank of the node specified by key Note that the rank is 1-based integer. Rank 1 means the first node

If the node is not found, 0 is returned. Otherwise rank(> 0) is returned

Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) GetByKey

func (this *SortedSet[K, SCORE, V]) GetByKey(key K) *SortedSetNode[K, SCORE, V]

Get node by key

If node is not found, nil is returned Time complexity : O(1)

func (*SortedSet[K, SCORE, V]) GetByRank

func (this *SortedSet[K, SCORE, V]) GetByRank(rank int, remove bool) *SortedSetNode[K, SCORE, V]

Get node by rank. Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node;

If remove is true, the returned nodes are removed If node is not found at specific rank, nil is returned

Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) GetCount

func (this *SortedSet[K, SCORE, V]) GetCount() int

Get the number of elements

func (*SortedSet[K, SCORE, V]) GetRangeByRank

func (this *SortedSet[K, SCORE, V]) GetRangeByRank(start int, end int, remove bool) []*SortedSetNode[K, SCORE, V]

Get nodes within specific rank range [start, end] Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node;

If start is greater than end, the returned array is in reserved order If remove is true, the returned nodes are removed

Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) GetRangeByScore

func (this *SortedSet[K, SCORE, V]) GetRangeByScore(start SCORE, end SCORE, options *GetRangeByScoreOptions) []*SortedSetNode[K, SCORE, V]

Get the nodes whose score within the specific range

If options is nil, it searchs in interval [start, end] without any limit by default

Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) Has

func (this *SortedSet[K, SCORE, V]) Has(key K) bool

Has reports whether key is a member of the set. It is the only method safe to call from a goroutine other than the one owning the set (it reads the sync.Map only, never skiplist structure or node fields).

func (*SortedSet[K, SCORE, V]) IterFuncRangeByRank

func (this *SortedSet[K, SCORE, V]) IterFuncRangeByRank(start int, end int, fn func(key K, value V) bool)

IterFuncRangeByRank apply fn to node within specific rank range [start, end] or until fn return false

Note that the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node; If start is greater than end, apply fn in reserved order If fn is nil, this function return without doing anything

func (*SortedSet[K, SCORE, V]) PeekMax

func (this *SortedSet[K, SCORE, V]) PeekMax() *SortedSetNode[K, SCORE, V]

get the element with maximum score, nil if the set is empty Time Complexity : O(1)

func (*SortedSet[K, SCORE, V]) PeekMin

func (this *SortedSet[K, SCORE, V]) PeekMin() *SortedSetNode[K, SCORE, V]

get the element with minimum score, nil if the set is empty Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) PopMax

func (this *SortedSet[K, SCORE, V]) PopMax() *SortedSetNode[K, SCORE, V]

get and remove the element with maximum score, nil if the set is empty Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) PopMin

func (this *SortedSet[K, SCORE, V]) PopMin() *SortedSetNode[K, SCORE, V]

get and remove the element with minimal score, nil if the set is empty Time complexity of this method is : O(log(N))

func (*SortedSet[K, SCORE, V]) Remove

func (this *SortedSet[K, SCORE, V]) Remove(key K) *SortedSetNode[K, SCORE, V]

Delete element specified by key

Time complexity of this method is : O(log(N))

type SortedSetLevel

type SortedSetLevel[K constraints.Ordered, SCORE constraints.Ordered, V any] struct {
	// contains filtered or unexported fields
}

type SortedSetNode

type SortedSetNode[K constraints.Ordered, SCORE constraints.Ordered, V any] struct {
	Value V // associated data
	// contains filtered or unexported fields
}

Node in skip list

func (*SortedSetNode[K, SCORE, V]) Key

func (this *SortedSetNode[K, SCORE, V]) Key() K

Get the key of the node

func (*SortedSetNode[K, SCORE, V]) Score

func (this *SortedSetNode[K, SCORE, V]) Score() SCORE

Get the score of the node

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL