File: skiplist.tcl

package info (click to toggle)
tcllib 1.20%2Bdfsg-1
  • links: PTS
  • area: main
  • in suites: bullseye
  • size: 68,064 kB
  • sloc: tcl: 216,842; ansic: 14,250; sh: 2,846; xml: 1,766; yacc: 1,145; pascal: 881; makefile: 107; perl: 84; f90: 84; python: 33; ruby: 13; php: 11
file content (437 lines) | stat: -rw-r--r-- 11,083 bytes parent folder | download | duplicates (9)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# skiplist.tcl --
#
#	Implementation of a skiplist data structure for Tcl.
#
#	To quote the inventor of skip lists, William Pugh:
#		Skip lists are 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.
#
#	For more details on how skip lists work, see Pugh, William. Skip
#	lists: a probabilistic alternative to balanced trees in
#	Communications of the ACM, June 1990, 33(6) 668-676. Also, see
#	ftp://ftp.cs.umd.edu/pub/skipLists/
# 
# Copyright (c) 2000 by Keith Vetter
# This software is licensed under a BSD license as described in tcl/tk
# license.txt file but with the copyright held by Keith Vetter.
#
# TODO:
#	customize key comparison to a user supplied routine

namespace eval ::struct {}

namespace eval ::struct::skiplist {
    # Data storage in the skiplist module
    # -------------------------------
    #
    # For each skiplist, we have the following arrays
    #   state - holds the current level plus some magic constants
    #	nodes - all the nodes in the skiplist, including a dummy header node
    
    # counter is used to give a unique name for unnamed skiplists
    variable counter 0

    # Internal constants
    variable MAXLEVEL 16
    variable PROB .5
    variable MAXINT [expr {0x7FFFFFFF}]

    # commands is the list of subcommands recognized by the skiplist
    variable commands [list \
	    "destroy"	\
	    "delete"	\
	    "insert"	\
	    "search"	\
	    "size"	\
	    "walk"	\
	    ]

    # State variables that can be set in the instantiation
    variable vars [list maxlevel probability]
    
    # Only export one command, the one used to instantiate a new skiplist
    namespace export skiplist
}

# ::struct::skiplist::skiplist --
#
#	Create a new skiplist with a given name; if no name is given, use
#	skiplistX, where X is a number.
#
# Arguments:
#	name	name of the skiplist; if null, generate one.
#
# Results:
#	name	name of the skiplist created

proc ::struct::skiplist::skiplist {{name ""} args} {
    set usage "skiplist name ?-maxlevel ##? ?-probability ##?"
    variable counter
    
    if { [llength [info level 0]] == 1 } {
	incr counter
	set name "skiplist${counter}"
    }

    if { ![string equal [info commands ::$name] ""] } {
	error "command \"$name\" already exists, unable to create skiplist"
    }

    # Handle the optional arguments
    set more_eval ""
    for {set i 0} {$i < [llength $args]} {incr i} {
	set flag [lindex $args $i]
	incr i
	if { $i >= [llength $args] } {
	    error "value for \"$flag\" missing: should be \"$usage\""
	}
	set value [lindex $args $i]
	switch -glob -- $flag {
	    "-maxl*" {
		set n [catch {set value [expr $value]}]
		if {$n || $value <= 0} {
		    error "value for the maxlevel option must be greater than 0"
		}
		append more_eval "; set state(maxlevel) $value"
	    }
	    "-prob*" {
		set n [catch {set value [expr $value]}]
		if {$n || $value <= 0 || $value >= 1} {
		    error "probability must be between 0 and 1"
		}
		append more_eval "; set state(prob) $value"
	    }
	    default {
		error "unknown option \"$flag\": should be \"$usage\""
	    }
	}
    }
    
    # Set up the namespace for this skiplist
    namespace eval ::struct::skiplist::skiplist$name {
	variable state
	variable nodes

	# NB. maxlevel and prob may be overridden by $more_eval at the end
	set state(maxlevel) $::struct::skiplist::MAXLEVEL
	set state(prob) $::struct::skiplist::PROB
	set state(level) 1
	set state(cnt) 0
	set state(size) 0

	set nodes(nil,key) $::struct::skiplist::MAXINT
	set nodes(header,key) "---"
	set nodes(header,value) "---"

	for {set i 1} {$i < $state(maxlevel)} {incr i} {
	    set nodes(header,$i) nil
	}
    } $more_eval

    # Create the command to manipulate the skiplist
    interp alias {} ::$name {} ::struct::skiplist::SkiplistProc $name

    return $name
}

###########################
# Private functions follow

# ::struct::skiplist::SkiplistProc --
#
#	Command that processes all skiplist object commands.
#
# Arguments:
#	name	name of the skiplist object to manipulate.
#	args	command name and args for the command
#
# Results:
#	Varies based on command to perform

proc ::struct::skiplist::SkiplistProc {name {cmd ""} args} {
    # Do minimal args checks here
    if { [llength [info level 0]] == 2 } {
	error "wrong # args: should be \"$name option ?arg arg ...?\""
    }
    
    # Split the args into command and args components
    if { [llength [info commands ::struct::skiplist::_$cmd]] == 0 } {
	variable commands
	set optlist [join $commands ", "]
	set optlist [linsert $optlist "end-1" "or"]
	error "bad option \"$cmd\": must be $optlist"
    }
    eval [linsert $args 0 ::struct::skiplist::_$cmd $name]
}

## ::struct::skiplist::_destroy --
#
#	Destroy a skiplist, including its associated command and data storage.
#
# Arguments:
#	name	name of the skiplist.
#
# Results:
#	None.

proc ::struct::skiplist::_destroy {name} {
    namespace delete ::struct::skiplist::skiplist$name
    interp alias {} ::$name {}
}

# ::struct::skiplist::_search --
#
#	Searches for a key in a skiplist
#
# Arguments:
#	name		name of the skiplist.
#	key		key for the node to search for
#
# Results:
#	0 if not found
#	[list 1 node_value] if found

proc ::struct::skiplist::_search {name key} {
    upvar ::struct::skiplist::skiplist${name}::state state
    upvar ::struct::skiplist::skiplist${name}::nodes nodes

    set x header
    for {set i $state(level)} {$i >= 1} {incr i -1} {
	while {1} {
	    set fwd $nodes($x,$i)
	    if {$nodes($fwd,key) == $::struct::skiplist::MAXINT} break
	    if {$nodes($fwd,key) >= $key} break
	    set x $fwd
	}
    }
    set x $nodes($x,1)
    if {$nodes($x,key) == $key} {
	return [list 1 $nodes($x,value)]
    }
    return 0
}

# ::struct::skiplist::_insert --
#
#	Add a node to a skiplist.
#
# Arguments:
#	name		name of the skiplist.
#	key		key for the node to insert
#	value		value of the node to insert
#
# Results:
#	0      if new node was created
#       level  if existing node was updated

proc ::struct::skiplist::_insert {name key value} {
    upvar ::struct::skiplist::skiplist${name}::state state
    upvar ::struct::skiplist::skiplist${name}::nodes nodes
    
    set x header
    for {set i $state(level)} {$i >= 1} {incr i -1} {
	while {1} {
	    set fwd $nodes($x,$i)
	    if {$nodes($fwd,key) == $::struct::skiplist::MAXINT} break
	    if {$nodes($fwd,key) >= $key} break
	    set x $fwd
	}
	set update($i) $x
    }
    set x $nodes($x,1)

    # Does the node already exist?
    if {$nodes($x,key) == $key} {
	set nodes($x,value) $value
	return 0
    }

    # Here to insert item
    incr state(size)
    set lvl [randomLevel $state(prob) $state(level) $state(maxlevel)]

    # Did the skip list level increase???
    if {$lvl > $state(level)} {
	for {set i [expr {$state(level) + 1}]} {$i <= $lvl} {incr i} {
	    set update($i) header
	}
	set state(level) $lvl
    }

    # Create a unique new node name and fill in the key, value parts
    set x [incr state(cnt)] 
    set nodes($x,key) $key
    set nodes($x,value) $value

    for {set i 1} {$i <= $lvl} {incr i} {
	set nodes($x,$i) $nodes($update($i),$i)
	set nodes($update($i),$i) $x
    }

    return $lvl
}

# ::struct::skiplist::_delete --
#
#	Deletes a node from a skiplist
#
# Arguments:
#	name		name of the skiplist.
#	key		key for the node to delete
#
# Results:
#	1 if we deleted a node
#       0 otherwise

proc ::struct::skiplist::_delete {name key} {
    upvar ::struct::skiplist::skiplist${name}::state state
    upvar ::struct::skiplist::skiplist${name}::nodes nodes
    
    set x header
    for {set i $state(level)} {$i >= 1} {incr i -1} {
	while {1} {
	    set fwd $nodes($x,$i)
	    if {$nodes($fwd,key) >= $key} break
	    set x $fwd
	}
	set update($i) $x
    }
    set x $nodes($x,1)

    # Did we find a node to delete?
    if {$nodes($x,key) != $key} {
	return 0
    }
    
    # Here when we found a node to delete
    incr state(size) -1
    
    # Unlink this node from all the linked lists that include to it
    for {set i 1} {$i <= $state(level)} {incr i} {
	set fwd $nodes($update($i),$i)
	if {$nodes($fwd,key) != $key} break
	set nodes($update($i),$i) $nodes($x,$i)
    }
    
    # Delete all traces of this node
    foreach v [array names nodes($x,*)] {
	unset nodes($v)
    }

    # Fix up the level in case it went down
    while {$state(level) > 1} {
	if {! [string equal "nil" $nodes(header,$state(level))]} break
	incr state(level) -1
    }

    return 1
}

# ::struct::skiplist::_size --
#
#	Returns how many nodes are in the skiplist
#
# Arguments:
#	name		name of the skiplist.
#
# Results:
#	number of nodes in the skiplist

proc ::struct::skiplist::_size {name} {
    upvar ::struct::skiplist::skiplist${name}::state state

    return $state(size)
}

# ::struct::skiplist::_walk --
#
#	Walks a skiplist performing a specified command on each node.
#	Command is executed at the global level with the actual command
#	executed is:  command key value
#
# Arguments:
#	name	name of the skiplist.
#	cmd		command to run on each node
#
# Results:
#	none.

proc ::struct::skiplist::_walk {name cmd} {
    upvar ::struct::skiplist::skiplist${name}::nodes nodes

    for {set x $nodes(header,1)} {$x != "nil"} {set x $nodes($x,1)} {
	# Evaluate the command at this node
	set cmdcpy $cmd
	lappend cmdcpy $nodes($x,key) $nodes($x,value)
	uplevel 2 $cmdcpy
    }
}

# ::struct::skiplist::randomLevel --
#
#	Generates a random level for a new node. We limit it to 1 greater
#	than the current level. 
#
# Arguments:
#	prob		probability to use in generating level
#	level		current biggest level
#	maxlevel	biggest possible level
#
# Results:
#	an integer between 1 and $maxlevel

proc ::struct::skiplist::randomLevel {prob level maxlevel} {

    set lvl 1
    while {(rand() < $prob) && ($lvl < $maxlevel)} {
	incr lvl
    }

    if {$lvl > $level} {
	set lvl [expr {$level + 1}]
    }
    
    return $lvl
}

# ::struct::skiplist::_dump --
#
#	Dumps out a skip list. Useful for debugging.
#
# Arguments:
#	name	name of the skiplist.
#
# Results:
#	none.

proc ::struct::skiplist::_dump {name} {
    upvar ::struct::skiplist::skiplist${name}::state state
    upvar ::struct::skiplist::skiplist${name}::nodes nodes


    puts "Current level $state(level)"
    puts "Maxlevel:     $state(maxlevel)"
    puts "Probability:  $state(prob)"
    puts ""
    puts "NODE    KEY  FORWARD"
    for {set x header} {$x != "nil"} {set x $nodes($x,1)} {
	puts -nonewline [format "%-6s  %3s %4s" $x $nodes($x,key) $nodes($x,1)]
	for {set i 2} {[info exists nodes($x,$i)]} {incr i} {
	    puts -nonewline [format %4s $nodes($x,$i)]
	}
	puts ""
    }
}

# ### ### ### ######### ######### #########
## Ready

namespace eval ::struct {
    # Get 'skiplist::skiplist' into the general structure namespace.
    namespace import -force skiplist::skiplist
    namespace export skiplist
}
package provide struct::skiplist 1.3