Skip to content

Commit a21e687

Browse files
committed
feat: add Skip List data structure
1 parent 5c39e87 commit a21e687

2 files changed

Lines changed: 452 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
/**
2+
* Skip List
3+
*
4+
* A skip list is a probabilistic data structure that maintains an ordered set
5+
* of keys and supports search, insertion and deletion in expected O(log n)
6+
* time. It is built from a stack of sorted linked lists where each higher
7+
* level skips over more elements, acting as an "express lane" into the level
8+
* below. Each newly inserted node is promoted to the next level with a fixed
9+
* probability `p` (typically 1/2), giving an expected height of log_{1/p}(n).
10+
*
11+
* Compared to balanced binary search trees, skip lists are simpler to
12+
* implement and require no rotations to maintain balance.
13+
*
14+
* Reference: https://en.wikipedia.org/wiki/Skip_list
15+
*/
16+
17+
const DEFAULT_MAX_LEVEL = 16
18+
const DEFAULT_P = 0.5
19+
20+
class SkipListNode {
21+
/**
22+
* @param {*} key - Ordered key stored at this node, or `null` for the head sentinel.
23+
* @param {*} value - Arbitrary value associated with `key`.
24+
* @param {number} level - Height of the node (number of forward pointers).
25+
*/
26+
constructor(key, value, level) {
27+
this.key = key
28+
this.value = value
29+
this.forward = new Array(level + 1).fill(null)
30+
}
31+
}
32+
33+
class SkipList {
34+
/**
35+
* Create an empty skip list.
36+
*
37+
* @param {object} [options] - Optional tuning parameters.
38+
* @param {number} [options.maxLevel=16] - Maximum number of levels (>= 1).
39+
* @param {number} [options.p=0.5] - Promotion probability in (0, 1).
40+
* @param {() => number} [options.random=Math.random] - RNG returning a value in [0, 1); injectable for deterministic tests.
41+
*
42+
* @example
43+
* const list = new SkipList()
44+
* list.insert(3, 'three')
45+
* list.search(3) // 'three'
46+
*/
47+
constructor(options = {}) {
48+
const {
49+
maxLevel = DEFAULT_MAX_LEVEL,
50+
p = DEFAULT_P,
51+
random = Math.random
52+
} = options
53+
54+
if (!Number.isInteger(maxLevel) || maxLevel < 1) {
55+
throw new RangeError('maxLevel must be a positive integer')
56+
}
57+
if (typeof p !== 'number' || p <= 0 || p >= 1) {
58+
throw new RangeError('p must be a number in the open interval (0, 1)')
59+
}
60+
if (typeof random !== 'function') {
61+
throw new TypeError(
62+
'random must be a function returning a value in [0, 1)'
63+
)
64+
}
65+
66+
this.maxLevel = maxLevel
67+
this.p = p
68+
this.random = random
69+
this.level = 0
70+
this.length = 0
71+
this.head = new SkipListNode(null, null, maxLevel)
72+
}
73+
74+
/**
75+
* Pick a random level in [0, maxLevel] using geometric distribution.
76+
*
77+
* @returns {number} The chosen level for a new node.
78+
* @private
79+
*/
80+
_randomLevel() {
81+
let lvl = 0
82+
while (this.random() < this.p && lvl < this.maxLevel) {
83+
lvl++
84+
}
85+
return lvl
86+
}
87+
88+
/**
89+
* Compare two keys using `<` and `>`. Numbers, strings and any type that
90+
* supports the relational operators consistently can be used as keys.
91+
*
92+
* @param {*} a - Left-hand key.
93+
* @param {*} b - Right-hand key.
94+
* @returns {number} `-1` if `a < b`, `1` if `a > b`, `0` otherwise.
95+
* @private
96+
*/
97+
_compare(a, b) {
98+
if (a < b) return -1
99+
if (a > b) return 1
100+
return 0
101+
}
102+
103+
/**
104+
* Insert a key/value pair. If the key already exists, its value is updated
105+
* in place and the size of the list does not change.
106+
*
107+
* @param {*} key - Key to insert; must be comparable with existing keys.
108+
* @param {*} [value] - Value associated with the key (defaults to the key).
109+
* @returns {SkipList} The list, for chaining.
110+
*
111+
* @example
112+
* const list = new SkipList()
113+
* list.insert(1, 'a').insert(2, 'b')
114+
* list.size() // 2
115+
*/
116+
insert(key, value = key) {
117+
const update = new Array(this.maxLevel + 1).fill(this.head)
118+
let current = this.head
119+
120+
for (let i = this.level; i >= 0; i--) {
121+
while (
122+
current.forward[i] !== null &&
123+
this._compare(current.forward[i].key, key) < 0
124+
) {
125+
current = current.forward[i]
126+
}
127+
update[i] = current
128+
}
129+
130+
current = current.forward[0]
131+
132+
if (current !== null && this._compare(current.key, key) === 0) {
133+
current.value = value
134+
return this
135+
}
136+
137+
const lvl = this._randomLevel()
138+
if (lvl > this.level) {
139+
for (let i = this.level + 1; i <= lvl; i++) {
140+
update[i] = this.head
141+
}
142+
this.level = lvl
143+
}
144+
145+
const node = new SkipListNode(key, value, lvl)
146+
for (let i = 0; i <= lvl; i++) {
147+
node.forward[i] = update[i].forward[i]
148+
update[i].forward[i] = node
149+
}
150+
this.length++
151+
return this
152+
}
153+
154+
/**
155+
* Look up the value associated with a key.
156+
*
157+
* @param {*} key - Key to search for.
158+
* @returns {*} The associated value, or `undefined` if `key` is absent.
159+
*
160+
* @example
161+
* const list = new SkipList()
162+
* list.insert('apple', 1)
163+
* list.search('apple') // 1
164+
* list.search('banana') // undefined
165+
*/
166+
search(key) {
167+
let current = this.head
168+
for (let i = this.level; i >= 0; i--) {
169+
while (
170+
current.forward[i] !== null &&
171+
this._compare(current.forward[i].key, key) < 0
172+
) {
173+
current = current.forward[i]
174+
}
175+
}
176+
current = current.forward[0]
177+
if (current !== null && this._compare(current.key, key) === 0) {
178+
return current.value
179+
}
180+
return undefined
181+
}
182+
183+
/**
184+
* Check whether a key is present.
185+
*
186+
* @param {*} key - Key to test.
187+
* @returns {boolean} `true` if the key is present.
188+
*
189+
* @example
190+
* const list = new SkipList()
191+
* list.insert(7)
192+
* list.has(7) // true
193+
*/
194+
has(key) {
195+
let current = this.head
196+
for (let i = this.level; i >= 0; i--) {
197+
while (
198+
current.forward[i] !== null &&
199+
this._compare(current.forward[i].key, key) < 0
200+
) {
201+
current = current.forward[i]
202+
}
203+
}
204+
current = current.forward[0]
205+
return current !== null && this._compare(current.key, key) === 0
206+
}
207+
208+
/**
209+
* Remove the entry with the given key.
210+
*
211+
* @param {*} key - Key to delete.
212+
* @returns {boolean} `true` if a node was removed, `false` if the key was absent.
213+
*
214+
* @example
215+
* const list = new SkipList()
216+
* list.insert(1).insert(2)
217+
* list.delete(1) // true
218+
* list.delete(1) // false
219+
*/
220+
delete(key) {
221+
const update = new Array(this.maxLevel + 1).fill(this.head)
222+
let current = this.head
223+
224+
for (let i = this.level; i >= 0; i--) {
225+
while (
226+
current.forward[i] !== null &&
227+
this._compare(current.forward[i].key, key) < 0
228+
) {
229+
current = current.forward[i]
230+
}
231+
update[i] = current
232+
}
233+
234+
current = current.forward[0]
235+
if (current === null || this._compare(current.key, key) !== 0) {
236+
return false
237+
}
238+
239+
for (let i = 0; i <= this.level; i++) {
240+
if (update[i].forward[i] !== current) break
241+
update[i].forward[i] = current.forward[i]
242+
}
243+
244+
while (this.level > 0 && this.head.forward[this.level] === null) {
245+
this.level--
246+
}
247+
this.length--
248+
return true
249+
}
250+
251+
/**
252+
* Number of distinct keys in the list.
253+
*
254+
* @returns {number} The current size.
255+
*
256+
* @example
257+
* const list = new SkipList()
258+
* list.insert(1).insert(2).insert(2)
259+
* list.size() // 2
260+
*/
261+
size() {
262+
return this.length
263+
}
264+
265+
/**
266+
* Whether the list contains no keys.
267+
*
268+
* @returns {boolean} `true` when empty.
269+
*/
270+
isEmpty() {
271+
return this.length === 0
272+
}
273+
274+
/**
275+
* Yield `[key, value]` pairs in ascending key order.
276+
*
277+
* @returns {Generator<[*, *]>} Iterator over the entries of the list.
278+
*
279+
* @example
280+
* const list = new SkipList()
281+
* list.insert(2, 'b').insert(1, 'a')
282+
* Array.from(list.entries()) // [[1, 'a'], [2, 'b']]
283+
*/
284+
*entries() {
285+
let current = this.head.forward[0]
286+
while (current !== null) {
287+
yield [current.key, current.value]
288+
current = current.forward[0]
289+
}
290+
}
291+
292+
/**
293+
* Default iterator yielding keys in ascending order.
294+
*
295+
* @returns {Generator<*>} Iterator over keys.
296+
*
297+
* @example
298+
* const list = new SkipList()
299+
* list.insert(3).insert(1).insert(2)
300+
* [...list] // [1, 2, 3]
301+
*/
302+
*[Symbol.iterator]() {
303+
let current = this.head.forward[0]
304+
while (current !== null) {
305+
yield current.key
306+
current = current.forward[0]
307+
}
308+
}
309+
}
310+
311+
export { SkipList, SkipListNode }

0 commit comments

Comments
 (0)