diff --git a/README.md b/README.md index a38a900..020979d 100644 --- a/README.md +++ b/README.md @@ -1063,7 +1063,7 @@ type Signed interface { } ``` -## type [SkipList]() +## type [SkipList]() SkipList is a probabilistic data structure that seem likely to supplant balanced trees as the implementation method of choice for many applications. Skip list algorithms have the same asymptotic expected time bounds as balanced trees and are simpler, faster and use less space. @@ -1075,7 +1075,7 @@ type SkipList[K any, V any] struct { } ``` -### func [NewSkipList]() +### func [NewSkipList]() ```go func NewSkipList[K Ordered, V any]() *SkipList[K, V] @@ -1083,7 +1083,7 @@ func NewSkipList[K Ordered, V any]() *SkipList[K, V] NewSkipList creates a new Skiplist. -### func [NewSkipListFromMap]() +### func [NewSkipListFromMap]() ```go func NewSkipListFromMap[K Ordered, V any](m map[K]V) *SkipList[K, V] @@ -1091,7 +1091,7 @@ func NewSkipListFromMap[K Ordered, V any](m map[K]V) *SkipList[K, V] NewSkipListFromMap create a new Skiplist from a map. -### func [NewSkipListFunc]() +### func [NewSkipListFunc]() ```go func NewSkipListFunc[K any, V any](keyCmp CompareFn[K]) *SkipList[K, V] @@ -1099,13 +1099,13 @@ func NewSkipListFunc[K any, V any](keyCmp CompareFn[K]) *SkipList[K, V] NewSkipListFunc creates a new Skiplist with specified compare function keyCmp. -### func \(\*SkipList\[K, V\]\) [Clear]() +### func \(\*SkipList\[K, V\]\) [Clear]() ```go func (sl *SkipList[K, V]) Clear() ``` -### func \(\*SkipList\[K, V\]\) [Find]() +### func \(\*SkipList\[K, V\]\) [Find]() ```go func (sl *SkipList[K, V]) Find(key K) *V @@ -1113,37 +1113,37 @@ func (sl *SkipList[K, V]) Find(key K) *V Find returns the value associated with the passed key if the key is in the skiplist, otherwise returns nil. -### func \(\*SkipList\[K, V\]\) [ForEach]() +### func \(\*SkipList\[K, V\]\) [ForEach]() ```go func (sl *SkipList[K, V]) ForEach(op func(K, V)) ``` -### func \(\*SkipList\[K, V\]\) [ForEachIf]() +### func \(\*SkipList\[K, V\]\) [ForEachIf]() ```go func (sl *SkipList[K, V]) ForEachIf(op func(K, V) bool) ``` -### func \(\*SkipList\[K, V\]\) [ForEachMutable]() +### func \(\*SkipList\[K, V\]\) [ForEachMutable]() ```go func (sl *SkipList[K, V]) ForEachMutable(op func(K, *V)) ``` -### func \(\*SkipList\[K, V\]\) [ForEachMutableIf]() +### func \(\*SkipList\[K, V\]\) [ForEachMutableIf]() ```go func (sl *SkipList[K, V]) ForEachMutableIf(op func(K, *V) bool) ``` -### func \(\*SkipList\[K, V\]\) [Has]() +### func \(\*SkipList\[K, V\]\) [Has]() ```go func (sl *SkipList[K, V]) Has(key K) bool ``` -### func \(\*SkipList\[K, V\]\) [Insert]() +### func \(\*SkipList\[K, V\]\) [Insert]() ```go func (sl *SkipList[K, V]) Insert(key K, value V) @@ -1151,19 +1151,19 @@ func (sl *SkipList[K, V]) Insert(key K, value V) Insert inserts a key\-value pair into the skiplist. If the key is already in the skip list, it's value will be updated. -### func \(\*SkipList\[K, V\]\) [IsEmpty]() +### func \(\*SkipList\[K, V\]\) [IsEmpty]() ```go func (sl *SkipList[K, V]) IsEmpty() bool ``` -### func \(\*SkipList\[K, V\]\) [Len]() +### func \(\*SkipList\[K, V\]\) [Len]() ```go func (sl *SkipList[K, V]) Len() int ``` -### func \(\*SkipList\[K, V\]\) [Remove]() +### func \(\*SkipList\[K, V\]\) [Remove]() ```go func (sl *SkipList[K, V]) Remove(key K) bool diff --git a/README_zh.md b/README_zh.md index 6e52f9d..f02a1d7 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1061,7 +1061,7 @@ type Signed interface { } ``` -## type [SkipList]() +## type [SkipList]() SkipList is a probabilistic data structure that seem likely to supplant balanced trees as the implementation method of choice for many applications. Skip list algorithms have the same asymptotic expected time bounds as balanced trees and are simpler, faster and use less space. @@ -1073,7 +1073,7 @@ type SkipList[K any, V any] struct { } ``` -### func [NewSkipList]() +### func [NewSkipList]() ```go func NewSkipList[K Ordered, V any]() *SkipList[K, V] @@ -1081,7 +1081,7 @@ func NewSkipList[K Ordered, V any]() *SkipList[K, V] NewSkipList creates a new Skiplist. -### func [NewSkipListFromMap]() +### func [NewSkipListFromMap]() ```go func NewSkipListFromMap[K Ordered, V any](m map[K]V) *SkipList[K, V] @@ -1089,7 +1089,7 @@ func NewSkipListFromMap[K Ordered, V any](m map[K]V) *SkipList[K, V] NewSkipListFromMap create a new Skiplist from a map. -### func [NewSkipListFunc]() +### func [NewSkipListFunc]() ```go func NewSkipListFunc[K any, V any](keyCmp CompareFn[K]) *SkipList[K, V] @@ -1097,13 +1097,13 @@ func NewSkipListFunc[K any, V any](keyCmp CompareFn[K]) *SkipList[K, V] NewSkipListFunc creates a new Skiplist with specified compare function keyCmp. -### func \(\*SkipList\[K, V\]\) [Clear]() +### func \(\*SkipList\[K, V\]\) [Clear]() ```go func (sl *SkipList[K, V]) Clear() ``` -### func \(\*SkipList\[K, V\]\) [Find]() +### func \(\*SkipList\[K, V\]\) [Find]() ```go func (sl *SkipList[K, V]) Find(key K) *V @@ -1111,37 +1111,37 @@ func (sl *SkipList[K, V]) Find(key K) *V Find returns the value associated with the passed key if the key is in the skiplist, otherwise returns nil. -### func \(\*SkipList\[K, V\]\) [ForEach]() +### func \(\*SkipList\[K, V\]\) [ForEach]() ```go func (sl *SkipList[K, V]) ForEach(op func(K, V)) ``` -### func \(\*SkipList\[K, V\]\) [ForEachIf]() +### func \(\*SkipList\[K, V\]\) [ForEachIf]() ```go func (sl *SkipList[K, V]) ForEachIf(op func(K, V) bool) ``` -### func \(\*SkipList\[K, V\]\) [ForEachMutable]() +### func \(\*SkipList\[K, V\]\) [ForEachMutable]() ```go func (sl *SkipList[K, V]) ForEachMutable(op func(K, *V)) ``` -### func \(\*SkipList\[K, V\]\) [ForEachMutableIf]() +### func \(\*SkipList\[K, V\]\) [ForEachMutableIf]() ```go func (sl *SkipList[K, V]) ForEachMutableIf(op func(K, *V) bool) ``` -### func \(\*SkipList\[K, V\]\) [Has]() +### func \(\*SkipList\[K, V\]\) [Has]() ```go func (sl *SkipList[K, V]) Has(key K) bool ``` -### func \(\*SkipList\[K, V\]\) [Insert]() +### func \(\*SkipList\[K, V\]\) [Insert]() ```go func (sl *SkipList[K, V]) Insert(key K, value V) @@ -1149,19 +1149,19 @@ func (sl *SkipList[K, V]) Insert(key K, value V) Insert inserts a key\-value pair into the skiplist. If the key is already in the skip list, it's value will be updated. -### func \(\*SkipList\[K, V\]\) [IsEmpty]() +### func \(\*SkipList\[K, V\]\) [IsEmpty]() ```go func (sl *SkipList[K, V]) IsEmpty() bool ``` -### func \(\*SkipList\[K, V\]\) [Len]() +### func \(\*SkipList\[K, V\]\) [Len]() ```go func (sl *SkipList[K, V]) Len() int ``` -### func \(\*SkipList\[K, V\]\) [Remove]() +### func \(\*SkipList\[K, V\]\) [Remove]() ```go func (sl *SkipList[K, V]) Remove(key K) bool diff --git a/skiplist.go b/skiplist.go index eb2a961..a32ccaf 100644 --- a/skiplist.go +++ b/skiplist.go @@ -25,10 +25,11 @@ const ( // // See https://en.wikipedia.org/wiki/Skip_list for more details. type SkipList[K any, V any] struct { - keyCmp CompareFn[K] - level int // Current level, may increase dynamically during insertion - len int // Total elements numner in the skiplist. - head skipListNode[K, V] + keyCmp CompareFn[K] + builtinCompare bool // Whether the key type is Ordered, so we can use builtin compare to improve performance. + level int // Current level, may increase dynamically during insertion + len int // Total elements numner in the skiplist. + head skipListNode[K, V] // This cache is used to save the previous nodes when modifying the skip list to avoid // allocating memory each time it is called. prevsCache []*skipListNode[K, V] // Cache to avoid memory allocation. @@ -46,7 +47,9 @@ type skipListNode[K any, V any] struct { // NewSkipList creates a new Skiplist. func NewSkipList[K Ordered, V any]() *SkipList[K, V] { - return NewSkipListFunc[K, V](OrderedCompare[K]) + sl := NewSkipListFunc[K, V](OrderedCompare[K]) + sl.builtinCompare = true + return sl } // NewSkipListFromMap create a new Skiplist from a map. @@ -192,7 +195,30 @@ func (sl *SkipList[K, V]) randomLevel() int { return level } -func (sl *SkipList[K, V]) findNode(key K) *skipListNode[K, V] { +//go:generate bash ./skiplist_findnode_generate.sh skiplist_findnode.go +// func (sl *SkipList[K, V]) findNode(key K) *skipListNode[K, V] + +// findNodeFast find node with builtin compare function, which is faster because it can be inlined. +func findNodeFast[K Ordered, V any](sl *SkipList[K, V], key K) *skipListNode[K, V] { + var pre = &sl.head + for i := sl.level - 1; i >= 0; i-- { + cur := pre.next[i] + for ; cur != nil; cur = cur.next[i] { + cmpRet := OrderedCompare(cur.key, key) + if cmpRet == 0 { + return cur + } + if cmpRet > 0 { + break + } + pre = cur + } + } + return nil +} + +// findNodeSlow use keyCmp to compare key, which is slower. +func (sl *SkipList[K, V]) findNodeSlow(key K) *skipListNode[K, V] { var pre = &sl.head for i := sl.level - 1; i >= 0; i-- { cur := pre.next[i] diff --git a/skiplist_findnode.go b/skiplist_findnode.go new file mode 100644 index 0000000..5dda55f --- /dev/null +++ b/skiplist_findnode.go @@ -0,0 +1,75 @@ +// AUTO GENERATED CODE, DON'T EDIT!!! +// EDIT skiplist_findnode_generate.sh accordingly. + +package stl4go + +import "unsafe" + +func forceCast[T any, F any](f *F) *T { + //#nosec G103 + return (*T)(unsafe.Pointer(f)) +} + +// findNode find the node which has specified key. +func (sl *SkipList[K, V]) findNode(key K) *skipListNode[K, V] { + if !sl.builtinCompare { + return sl.findNodeSlow(key) + } + // For knowned Ordered types, use findNodeFast to improve performance. + var iface interface{} = key + switch iface.(type) { + case int: + tsl := forceCast[SkipList[int, V]](sl) + v, _ := iface.(int) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case string: + tsl := forceCast[SkipList[string, V]](sl) + v, _ := iface.(string) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case int32: + tsl := forceCast[SkipList[int32, V]](sl) + v, _ := iface.(int32) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case uint32: + tsl := forceCast[SkipList[uint32, V]](sl) + v, _ := iface.(uint32) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case int64: + tsl := forceCast[SkipList[int64, V]](sl) + v, _ := iface.(int64) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case uint64: + tsl := forceCast[SkipList[uint64, V]](sl) + v, _ := iface.(uint64) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case int8: + tsl := forceCast[SkipList[int8, V]](sl) + v, _ := iface.(int8) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case uint8: + tsl := forceCast[SkipList[uint8, V]](sl) + v, _ := iface.(uint8) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case int16: + tsl := forceCast[SkipList[int16, V]](sl) + v, _ := iface.(int16) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case uint16: + tsl := forceCast[SkipList[uint16, V]](sl) + v, _ := iface.(uint16) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case uintptr: + tsl := forceCast[SkipList[uintptr, V]](sl) + v, _ := iface.(uintptr) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case float32: + tsl := forceCast[SkipList[float32, V]](sl) + v, _ := iface.(float32) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + case float64: + tsl := forceCast[SkipList[float64, V]](sl) + v, _ := iface.(float64) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v)) + } + return sl.findNodeSlow(key) +} diff --git a/skiplist_findnode_generate.sh b/skiplist_findnode_generate.sh new file mode 100755 index 0000000..f26605d --- /dev/null +++ b/skiplist_findnode_generate.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# The go generics sucks! +# We have to generate skiplist_newnode.go by myself. + +generate() { + # NOTE the tab chars, they are used to follow the go style guides. + echo "\ +package $GOPACKAGE + +import \"unsafe\"" + + echo " +func forceCast[T any, F any](f *F) *T { + //#nosec G103 + return (*T)(unsafe.Pointer(f)) +}" + + echo " +// findNode find the node which has specified key. +func (sl *SkipList[K, V]) findNode(key K) *skipListNode[K, V] { + if !sl.builtinCompare { + return sl.findNodeSlow(key) + } + // For knowned Ordered types, use findNodeFast to improve performance. + var iface interface{} = key + switch iface.(type) {" + + for type in int string int32 uint32 int64 uint64 int8 uint8 int16 uint16 uintptr float32 float64; do + echo "\ + case $type: + tsl := forceCast[SkipList[$type, V]](sl) + v, _ := iface.($type) + return forceCast[skipListNode[K, V]](findNodeFast(tsl, v))" + done + echo "\ + } + return sl.findNodeSlow(key) +}" + +} + +output_file=$1 + +echo -e "// AUTO GENERATED CODE, DON'T EDIT!!!\n// EDIT $(basename $0) accordingly.\n" > $output_file +generate >> $output_file diff --git a/skiplist_test.go b/skiplist_test.go index 149469b..ae77f55 100644 --- a/skiplist_test.go +++ b/skiplist_test.go @@ -13,6 +13,40 @@ func TestNewSkipList(t *testing.T) { NewSkipList[int, int]() } +func TestNewSkipListString(t *testing.T) { + sl := NewSkipList[string, int]() + sl.Insert("hello", 1) + expectTrue(t, sl.Has("hello")) + expectEq(t, *sl.Find("hello"), 1) + expectFalse(t, sl.Has("world")) + expectEq(t, sl.Find("world"), nil) +} + +func testNewSkipListType[T Numeric](t *testing.T) { + sl := NewSkipList[T, int]() + var n T = 1 + sl.Insert(n, 1) + expectTrue(t, sl.Has(n)) + expectEq(t, *sl.Find(n), 1) + expectFalse(t, sl.Has(n+1)) + expectEq(t, sl.Find(n+1), nil) +} + +func TestNewSkipListInt8(t *testing.T) { testNewSkipListType[int8](t) } +func TestNewSkipListInt16(t *testing.T) { testNewSkipListType[int16](t) } +func TestNewSkipListInt32(t *testing.T) { testNewSkipListType[int32](t) } +func TestNewSkipListInt64(t *testing.T) { testNewSkipListType[int64](t) } + +func TestNewSkipListUInt8(t *testing.T) { testNewSkipListType[uint8](t) } +func TestNewSkipListUInt16(t *testing.T) { testNewSkipListType[uint16](t) } +func TestNewSkipListUInt32(t *testing.T) { testNewSkipListType[uint32](t) } +func TestNewSkipListUInt64(t *testing.T) { testNewSkipListType[uint64](t) } + +func TestNewSkipListUIntPtr(t *testing.T) { testNewSkipListType[uintptr](t) } + +func TestNewSkipListFloat32(t *testing.T) { testNewSkipListType[float32](t) } +func TestNewSkipListFloat64(t *testing.T) { testNewSkipListType[float64](t) } + func TestNewSkipListFunc(t *testing.T) { type Person struct { name string @@ -29,6 +63,8 @@ func TestNewSkipListFunc(t *testing.T) { sl.Insert(Person{"zhangsan", 20}, 1) sl.Insert(Person{"lisi", 20}, 1) sl.Insert(Person{"wangwu", 30}, 1) + expectTrue(t, sl.Has(Person{"zhangsan", 20})) + expectFalse(t, sl.Has(Person{"zhangsan", 30})) var ps []Person sl.ForEach(func(p Person, _ int) { ps = append(ps, p)