Skip to content

Commit

Permalink
improve doc, add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
andremueller committed Jan 7, 2024
1 parent 621d415 commit cd8de02
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 6 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,39 @@

## Introduction

This is a new skip list library for Go.
This is a new skip list library for Go supporting access by a key and index.

## Objectives

- Using Go generics for key and value.
- Simple maintainable code
- Allowing fast random access (GetByPos) of the nth element by combining the basic skip list algorithm in with the linear list operations (c.f. 1. Section 3.4).
- Allowing fast random access (GetByPos) of the nth element by combining the basic skip list algorithm in with the linear list operations (c.f. 1. Section 3.4). The indexed access has an average runtime of $O(log(n))$.

## Installation

```bash
go get github.com/andremueller/goskiplist/pkg/skiplist
```



## Usage Example

```go
// creates a skip list with key type `int` and value type `string`
s := skiplist.NewSkipList[int, string]()

s.Set(1, "cat")
s.Set(2, "dog")

x, _ := s.Get(1)
fmt.Printf("Value: %s", x.Value)
// Output "Value: cat"

x, _ = s.GetByPos(1)
fmt.Printf("Value: %s", x.Value)
// Output "Value: dog"
```

## References

Expand Down
35 changes: 35 additions & 0 deletions pkg/skiplist/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package skiplist_test

import (
"fmt"

"github.com/andremueller/goskiplist/pkg/skiplist"
)

func ExampleNewSkipList() {
// creates a skip list with key type `int` and value type `string`
s := skiplist.NewSkipList[int, string]()

s.Set(1, "cat")
s.Set(2, "dog")

x, _ := s.Get(1)
fmt.Printf("Value: %s", x.Value)
// Output "Value: cat"
}

func ExampleSkipList_Set() {
// creates a skip list with key type `int` and value type `string`
s := skiplist.NewSkipList[int, string]()

// sets keys
s.Set(1, "cat")
s.Set(2, "dog")

// overrides the first key
s.Set(1, "worm")

x, _ := s.Get(1)
fmt.Printf("Value: %s", x.Value)
// Output "Value: worm"
}
4 changes: 3 additions & 1 deletion pkg/skiplist/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"fmt"
)

// Node holds an element within the SkipList with a unique key `key`.
type Node[K cmp.Ordered, V any] struct {
key K
Value V
Value V // Value is the payload within an element node.
next []*Node[K, V]
dist []int
}
Expand All @@ -32,6 +33,7 @@ func (n *Node[K, V]) Level() int {
return len(n.next)
}

// Next returns the adjacent element within the skip list. If there is no such element, nil is returned.
func (n *Node[K, V]) Next() *Node[K, V] {
if len(n.next) > 0 {
return n.next[0]
Expand Down
26 changes: 23 additions & 3 deletions pkg/skiplist/skiplist.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,28 @@ func defaultLevelFunc(p float64, maxLevel int) int {
return level
}

// SkipList is a structure implementing the skip list of William Pugh.
// It allows in addition to the standard key operations SkipList.Set(), SkipList.Get(), and SkipList.Remove()
// the indexed linear list operations SkipList.GetByPos() and SkipList.RemoveByPos().
// There are two generic parameters K is the key, which must be cmp.Ordered policy, and the value V can be of any type.
type SkipList[K cmp.Ordered, V any] struct {
p float64 // probability for increasing the level of the skip list
maxLevel int // maximum levels of the skip list
count int // count is the number of elements in the skip list
levelFunc LevelFunc // function for generating a random level
head *Node[K, V] // the head node of the skip list
levelFunc LevelFunc
}

type skipListOption[K cmp.Ordered, V any] func(*SkipList[K, V])

// WithLevelFunc adds a custom function for generating the level of each inserted element in the list.
func WithLevelFunc[K cmp.Ordered, V any](levelFunc LevelFunc) skipListOption[K, V] {
return func(s *SkipList[K, V]) {
s.levelFunc = levelFunc
}
}

// WithMaxLevel overrides the DefaultMaxLevel.
func WithMaxLevel[K cmp.Ordered, V any](maxLevel int) skipListOption[K, V] {
if maxLevel < 1 || maxLevel > MaxLevel {
log.Panic("Parameter maxLevel out of range (must be >=1 and <= MaxLevel)")
Expand All @@ -48,6 +54,7 @@ func WithMaxLevel[K cmp.Ordered, V any](maxLevel int) skipListOption[K, V] {
}
}

// WithProbability overrides the DefaultProbability.
func WithProbability[K cmp.Ordered, V any](prob float64) skipListOption[K, V] {
if prob < 0.01 || prob > 0.99 {
log.Panic("Parameter probability out of range (must be >= 0.01 and <= 0.99)")
Expand All @@ -57,6 +64,7 @@ func WithProbability[K cmp.Ordered, V any](prob float64) skipListOption[K, V] {
}
}

// NewSkipList creates a new empty SkipList object.
func NewSkipList[K cmp.Ordered, V any](options ...skipListOption[K, V]) *SkipList[K, V] {
var dummyKey K
var dummyValue V
Expand All @@ -75,10 +83,13 @@ func NewSkipList[K cmp.Ordered, V any](options ...skipListOption[K, V]) *SkipLis
return s
}

// First returns the first node of a skip list or nil if the list is empty. With the Node.Next() function
// the list can be iterated.
func (s *SkipList[K, V]) First() *Node[K, V] {
return s.head.Next()
}

// Size returns the number of elements within the skip list.
func (s *SkipList[K, V]) Size() int {
return s.count
}
Expand All @@ -94,7 +105,7 @@ func (s *SkipList[K, V]) randomLevel() int {

// Set sets the value `value` of a key `key` within the skip list.
// Replaces the value if the key was already added to the set or inserts the key if not.
// Return a reference to the node and its current position 0...n-1 within the skip list.
// Returns a reference to the node and its current position 0...n-1 within the skip list.
// The bool value is true, if a new node was created and false if the value was overridden.
func (s *SkipList[K, V]) Set(key K, value V) (*Node[K, V], int, bool) {
update := make([]*Node[K, V], s.Level(), s.maxLevel)
Expand Down Expand Up @@ -148,10 +159,11 @@ func (s *SkipList[K, V]) Set(key K, value V) (*Node[K, V], int, bool) {
return x, pos + 1, true
}

// InvalidPos is returned, when an element is not found within the skip list.
const InvalidPos = -1

// Get returns the node matching the searched key or nil if it was not found. The second return argument is the
// position 0...n-1 of the key
// position 0...n-1 of the key or InvalidPos if the element was not found.
func (s *SkipList[K, V]) Get(key K) (*Node[K, V], int) {
x := s.head
pos := -1
Expand All @@ -171,6 +183,10 @@ func (s *SkipList[K, V]) Get(key K) (*Node[K, V], int) {
return nil, InvalidPos
}

// GetByPos returns the kth element of the skip list where k must be in the interval [0, Size()).
// This operation is performed in O(log(n)) steps in the average due to the maintenance of the
// distance vectors within each element.
// Returns a node pointer to the element.
func (s *SkipList[K, V]) GetByPos(k int) *Node[K, V] {
if k < 0 || k >= s.count {
return nil
Expand All @@ -187,6 +203,8 @@ func (s *SkipList[K, V]) GetByPos(k int) *Node[K, V] {
return x
}

// Remove removes an element with key `key` from the skip list.
// Returns a reference to the removed element and its position 0...n-1 before it was removed.
func (s *SkipList[K, V]) Remove(key K) (*Node[K, V], int) {
update := make([]*Node[K, V], s.Level(), s.maxLevel)
updatePos := make([]int, s.Level(), s.maxLevel)
Expand Down Expand Up @@ -228,6 +246,8 @@ func (s *SkipList[K, V]) Remove(key K) (*Node[K, V], int) {
return nil, InvalidPos
}

// Remove removes an element at position k [0, Size()) from the skip list.
// Returns a reference to the removed element.
func (s *SkipList[K, V]) RemoveByPos(k int) *Node[K, V] {
if k < 0 || k >= s.count {
return nil
Expand Down

0 comments on commit cd8de02

Please sign in to comment.