Skip to content

Commit 406ce44

Browse files
committed
skipmaplist
1 parent 7c7c8d4 commit 406ce44

2 files changed

Lines changed: 295 additions & 3 deletions

File tree

collection/skiplist/skipmaplist.go

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,127 @@
1414

1515
package skiplist
1616

17-
// SkipMapList 本质是一个跳表,但只在0层存储了全部数据,上层只存储Key
18-
// todo
19-
type SkipMapList struct {
17+
import (
18+
"github.com/igevin/algokit/comparator"
19+
)
20+
21+
// skipMapNode represents a node in the SkipMapList.
22+
// @internal
23+
type skipMapNode[K, V any] struct {
24+
key K
25+
val V
26+
forward []*skipMapNode[K, V]
27+
}
28+
29+
// SkipMapList is a map-like data structure implemented with a skip list.
30+
// It stores key-value pairs, sorted by key.
31+
// Level 0 contains all key-value pairs, while upper levels only store keys as indices for fast searching.
32+
// For simplicity, this implementation uses the same node structure for all levels,
33+
// but logically the value is only relevant at level 0.
34+
type SkipMapList[K, V any] struct {
35+
header *skipMapNode[K, V]
36+
levels int
37+
length int
38+
compare comparator.Compare[K]
39+
}
40+
41+
// NewSkipMapList creates and initializes a new SkipMapList.
42+
// It requires a comparator function for the key type K.
43+
func NewSkipMapList[K, V any](c comparator.Compare[K]) *SkipMapList[K, V] {
44+
var k K
45+
var v V
46+
return &SkipMapList[K, V]{
47+
header: &skipMapNode[K, V]{key: k, val: v, forward: make([]*skipMapNode[K, V], maxLevel)},
48+
levels: 1,
49+
length: 0,
50+
compare: c,
51+
}
52+
}
53+
54+
// Get retrieves the value associated with the given key.
55+
// It returns the value and true if the key is found, otherwise it returns the zero value for V and false.
56+
func (s *SkipMapList[K, V]) Get(key K) (V, bool) {
57+
p := s.header
58+
for i := s.levels - 1; i >= 0; i-- {
59+
for p.forward[i] != nil && s.compare(p.forward[i].key, key) < 0 {
60+
p = p.forward[i]
61+
}
62+
}
63+
p = p.forward[0]
64+
if p != nil && s.compare(p.key, key) == 0 {
65+
return p.val, true
66+
}
67+
var v V
68+
return v, false
69+
}
70+
71+
// Put inserts or updates a key-value pair in the skip list.
72+
// If the key already exists, its value is updated.
73+
func (s *SkipMapList[K, V]) Put(key K, val V) {
74+
update := make([]*skipMapNode[K, V], maxLevel)
75+
p := s.header
76+
for i := s.levels - 1; i >= 0; i-- {
77+
for p.forward[i] != nil && s.compare(p.forward[i].key, key) < 0 {
78+
p = p.forward[i]
79+
}
80+
update[i] = p
81+
}
82+
p = p.forward[0]
83+
84+
// If key already exists, update the value
85+
if p != nil && s.compare(p.key, key) == 0 {
86+
p.val = val
87+
return
88+
}
89+
90+
// If key does not exist, insert a new node
91+
level := randomLevel()
92+
if level > s.levels {
93+
for i := s.levels; i < level; i++ {
94+
update[i] = s.header
95+
}
96+
s.levels = level
97+
}
98+
99+
newNode := &skipMapNode[K, V]{key: key, val: val, forward: make([]*skipMapNode[K, V], level)}
100+
for i := 0; i < level; i++ {
101+
newNode.forward[i] = update[i].forward[i]
102+
update[i].forward[i] = newNode
103+
}
104+
s.length++
105+
}
106+
107+
// Delete removes the key-value pair associated with the given key.
108+
func (s *SkipMapList[K, V]) Delete(key K) {
109+
update := make([]*skipMapNode[K, V], maxLevel)
110+
p := s.header
111+
for i := s.levels - 1; i >= 0; i-- {
112+
for p.forward[i] != nil && s.compare(p.forward[i].key, key) < 0 {
113+
p = p.forward[i]
114+
}
115+
update[i] = p
116+
}
117+
p = p.forward[0]
118+
119+
if p == nil || s.compare(p.key, key) != 0 {
120+
return // Key not found
121+
}
122+
123+
for i := 0; i < s.levels; i++ {
124+
if update[i].forward[i] != p {
125+
break
126+
}
127+
update[i].forward[i] = p.forward[i]
128+
}
129+
130+
// Adjust levels if the top levels become empty
131+
for s.levels > 1 && s.header.forward[s.levels-1] == nil {
132+
s.levels--
133+
}
134+
s.length--
135+
}
136+
137+
// Len returns the number of key-value pairs in the skip list.
138+
func (s *SkipMapList[K, V]) Len() int {
139+
return s.length
20140
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package skiplist
2+
3+
import (
4+
"testing"
5+
6+
"github.com/igevin/algokit/comparator"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestSkipMapList_Get(t *testing.T) {
12+
testCases := []struct {
13+
name string
14+
sml *SkipMapList[string, int]
15+
key string
16+
wantVal int
17+
wantOk bool
18+
}{
19+
{
20+
name: "empty list",
21+
sml: NewSkipMapList[string, int](comparator.PrimeComparator[string]),
22+
key: "a",
23+
wantOk: false,
24+
},
25+
{
26+
name: "get existing key",
27+
sml: func() *SkipMapList[string, int] {
28+
sml := NewSkipMapList[string, int](comparator.PrimeComparator[string])
29+
sml.Put("apple", 10)
30+
sml.Put("banana", 20)
31+
return sml
32+
}(),
33+
key: "apple",
34+
wantVal: 10,
35+
wantOk: true,
36+
},
37+
{
38+
name: "get non-existing key",
39+
sml: func() *SkipMapList[string, int] {
40+
sml := NewSkipMapList[string, int](comparator.PrimeComparator[string])
41+
sml.Put("apple", 10)
42+
sml.Put("banana", 20)
43+
return sml
44+
}(),
45+
key: "cherry",
46+
wantOk: false,
47+
},
48+
}
49+
50+
for _, tc := range testCases {
51+
t.Run(tc.name, func(t *testing.T) {
52+
val, ok := tc.sml.Get(tc.key)
53+
assert.Equal(t, tc.wantOk, ok)
54+
if ok {
55+
assert.Equal(t, tc.wantVal, val)
56+
}
57+
})
58+
}
59+
}
60+
61+
func TestSkipMapList_Put(t *testing.T) {
62+
testCases := []struct {
63+
name string
64+
sml *SkipMapList[string, int]
65+
key string
66+
val int
67+
wantLen int
68+
}{
69+
{
70+
name: "put to empty",
71+
sml: NewSkipMapList[string, int](comparator.PrimeComparator[string]),
72+
key: "apple",
73+
val: 10,
74+
wantLen: 1,
75+
},
76+
{
77+
name: "put new key",
78+
sml: func() *SkipMapList[string, int] {
79+
sml := NewSkipMapList[string, int](comparator.PrimeComparator[string])
80+
sml.Put("apple", 10)
81+
return sml
82+
}(),
83+
key: "banana",
84+
val: 20,
85+
wantLen: 2,
86+
},
87+
{
88+
name: "update existing key",
89+
sml: func() *SkipMapList[string, int] {
90+
sml := NewSkipMapList[string, int](comparator.PrimeComparator[string])
91+
sml.Put("apple", 10)
92+
return sml
93+
}(),
94+
key: "apple",
95+
val: 15,
96+
wantLen: 1,
97+
},
98+
}
99+
100+
for _, tc := range testCases {
101+
t.Run(tc.name, func(t *testing.T) {
102+
tc.sml.Put(tc.key, tc.val)
103+
assert.Equal(t, tc.wantLen, tc.sml.Len())
104+
val, ok := tc.sml.Get(tc.key)
105+
require.True(t, ok)
106+
assert.Equal(t, tc.val, val)
107+
})
108+
}
109+
}
110+
111+
func TestSkipMapList_Delete(t *testing.T) {
112+
testCases := []struct {
113+
name string
114+
sml *SkipMapList[int, string]
115+
key int
116+
wantLen int
117+
}{
118+
{
119+
name: "delete from empty",
120+
sml: NewSkipMapList[int, string](comparator.PrimeComparator[int]),
121+
key: 1,
122+
wantLen: 0,
123+
},
124+
{
125+
name: "delete existing",
126+
sml: func() *SkipMapList[int, string] {
127+
sml := NewSkipMapList[int, string](comparator.PrimeComparator[int])
128+
sml.Put(1, "one")
129+
sml.Put(2, "two")
130+
return sml
131+
}(),
132+
key: 1,
133+
wantLen: 1,
134+
},
135+
{
136+
name: "delete non-existing",
137+
sml: func() *SkipMapList[int, string] {
138+
sml := NewSkipMapList[int, string](comparator.PrimeComparator[int])
139+
sml.Put(1, "one")
140+
return sml
141+
}(),
142+
key: 2,
143+
wantLen: 1,
144+
},
145+
}
146+
147+
for _, tc := range testCases {
148+
t.Run(tc.name, func(t *testing.T) {
149+
tc.sml.Delete(tc.key)
150+
assert.Equal(t, tc.wantLen, tc.sml.Len())
151+
_, ok := tc.sml.Get(tc.key)
152+
assert.False(t, ok)
153+
})
154+
}
155+
}
156+
157+
func TestSkipMapList_Len(t *testing.T) {
158+
sml := NewSkipMapList[string, bool](comparator.PrimeComparator[string])
159+
assert.Equal(t, 0, sml.Len())
160+
161+
sml.Put("a", true)
162+
assert.Equal(t, 1, sml.Len())
163+
164+
sml.Put("b", true)
165+
assert.Equal(t, 2, sml.Len())
166+
167+
sml.Delete("a")
168+
assert.Equal(t, 1, sml.Len())
169+
170+
sml.Delete("b")
171+
assert.Equal(t, 0, sml.Len())
172+
}

0 commit comments

Comments
 (0)