1# prioqueue.tcl -- 2# 3# Priority Queue implementation for Tcl. 4# 5# adapted from queue.tcl 6# Copyright (c) 2002,2003 Michael Schlenker 7# Copyright (c) 2008 Alejandro Paz <vidriloco@gmail.com> 8# 9# See the file "license.terms" for information on usage and redistribution 10# of this file, and for a DISCLAIMER OF ALL WARRANTIES. 11# 12# RCS: @(#) $Id: prioqueue.tcl,v 1.10 2008/09/04 04:35:02 andreas_kupries Exp $ 13 14package require Tcl 8.2 15 16namespace eval ::struct {} 17 18namespace eval ::struct::prioqueue { 19 # The queues array holds all of the queues you've made 20 variable queues 21 22 # counter is used to give a unique name for unnamed queues 23 variable counter 0 24 25 # commands is the list of subcommands recognized by the queue 26 variable commands [list \ 27 "clear" \ 28 "destroy" \ 29 "get" \ 30 "peek" \ 31 "put" \ 32 "remove" \ 33 "size" \ 34 "peekpriority" \ 35 ] 36 37 variable sortopt [list \ 38 "-integer" \ 39 "-real" \ 40 "-ascii" \ 41 "-dictionary" \ 42 ] 43 44 # this is a simple design decision, that integer and real 45 # are sorted decreasing (-1), and -ascii and -dictionary are sorted -increasing (1) 46 # the values here map to the sortopt list 47 # could be changed to something configurable. 48 variable sortdir [list \ 49 "-1" \ 50 "-1" \ 51 "1" \ 52 "1" \ 53 ] 54 55 56 57 # Only export one command, the one used to instantiate a new queue 58 namespace export prioqueue 59 60 proc K {x y} {set x} ;# DKF's K combinator 61} 62 63# ::struct::prioqueue::prioqueue -- 64# 65# Create a new prioqueue with a given name; if no name is given, use 66# prioqueueX, where X is a number. 67# 68# Arguments: 69# sorting sorting option for lsort to use, no -command option 70# defaults to integer 71# name name of the queue; if null, generate one. 72# names may not begin with - 73# 74# 75# Results: 76# name name of the queue created 77 78proc ::struct::prioqueue::prioqueue {args} { 79 variable queues 80 variable counter 81 variable queues_sorting 82 variable sortopt 83 84 # check args 85 if {[llength $args] > 2} { 86 error "wrong # args: should be \"[lindex [info level 0] 0] ?-ascii|-dictionary|-integer|-real? ?name?\"" 87 } 88 if {[llength $args] == 0} { 89 # defaulting to integer priorities 90 set sorting -integer 91 } else { 92 if {[llength $args] == 1} { 93 if {[string match "-*" [lindex $args 0]]==1} { 94 set sorting [lindex $args 0] 95 } else { 96 set sorting -integer 97 set name [lindex $args 0] 98 } 99 } else { 100 if {[llength $args] == 2} { 101 foreach {sorting name} $args {break} 102 } 103 } 104 } 105 # check option (like lsort sorting options without -command) 106 if {[lsearch $sortopt $sorting] == -1} { 107 # if sortoption is unknown, but name is a sortoption we give a better error message 108 if {[info exists name] && [lsearch $sortopt $name]!=-1} { 109 error "wrong argument position: should be \"[lindex [info level 0] 0] ?-ascii|-dictionary|-integer|-real? ?name?\"" 110 } 111 error "unknown sort option \"$sorting\"" 112 } 113 # create name if not given 114 if {![info exists name]} { 115 incr counter 116 set name "prioqueue${counter}" 117 } 118 119 if { ![string equal [info commands ::$name] ""] } { 120 error "command \"$name\" already exists, unable to create prioqueue" 121 } 122 123 # Initialize the queue as empty 124 set queues($name) [list ] 125 switch -exact -- $sorting { 126 -integer { set queues_sorting($name) 0} 127 -real { set queues_sorting($name) 1} 128 -ascii { set queues_sorting($name) 2} 129 -dictionary { set queues_sorting($name) 3} 130 } 131 132 # Create the command to manipulate the queue 133 interp alias {} ::$name {} ::struct::prioqueue::QueueProc $name 134 135 return $name 136} 137 138########################## 139# Private functions follow 140 141# ::struct::prioqueue::QueueProc -- 142# 143# Command that processes all queue object commands. 144# 145# Arguments: 146# name name of the queue object to manipulate. 147# args command name and args for the command 148# 149# Results: 150# Varies based on command to perform 151 152proc ::struct::prioqueue::QueueProc {name {cmd ""} args} { 153 # Do minimal args checks here 154 if { [llength [info level 0]] == 2 } { 155 error "wrong # args: should be \"$name option ?arg arg ...?\"" 156 } 157 158 # Split the args into command and args components 159 if { [string equal [info commands ::struct::prioqueue::_$cmd] ""] } { 160 variable commands 161 set optlist [join $commands ", "] 162 set optlist [linsert $optlist "end-1" "or"] 163 error "bad option \"$cmd\": must be $optlist" 164 } 165 return [eval [linsert $args 0 ::struct::prioqueue::_$cmd $name]] 166} 167 168# ::struct::prioqueue::_clear -- 169# 170# Clear a queue. 171# 172# Arguments: 173# name name of the queue object. 174# 175# Results: 176# None. 177 178proc ::struct::prioqueue::_clear {name} { 179 variable queues 180 set queues($name) [list] 181 return 182} 183 184# ::struct::prioqueue::_destroy -- 185# 186# Destroy a queue object by removing it's storage space and 187# eliminating it's proc. 188# 189# Arguments: 190# name name of the queue object. 191# 192# Results: 193# None. 194 195proc ::struct::prioqueue::_destroy {name} { 196 variable queues 197 variable queues_sorting 198 unset queues($name) 199 unset queues_sorting($name) 200 interp alias {} ::$name {} 201 return 202} 203 204# ::struct::prioqueue::_get -- 205# 206# Get an item from a queue. 207# 208# Arguments: 209# name name of the queue object. 210# count number of items to get; defaults to 1 211# 212# Results: 213# item first count items from the queue; if there are not enough 214# items in the queue, throws an error. 215# 216 217proc ::struct::prioqueue::_get {name {count 1}} { 218 variable queues 219 if { $count < 1 } { 220 error "invalid item count $count" 221 } 222 223 if { $count > [llength $queues($name)] } { 224 error "insufficient items in prioqueue to fill request" 225 } 226 227 if { $count == 1 } { 228 # Handle this as a special case, so single item gets aren't listified 229 set item [lindex [lindex $queues($name) 0] 1] 230 set queues($name) [lreplace [K $queues($name) [set queues($name) ""]] 0 0] 231 return $item 232 } 233 234 # Otherwise, return a list of items 235 incr count -1 236 set items [lrange $queues($name) 0 $count] 237 foreach item $items { 238 lappend result [lindex $item 1] 239 } 240 set items "" 241 242 set queues($name) [lreplace [K $queues($name) [set queues($name) ""]] 0 $count] 243 return $result 244} 245 246# ::struct::prioqueue::_peek -- 247# 248# Retrive the value of an item on the queue without removing it. 249# 250# Arguments: 251# name name of the queue object. 252# count number of items to peek; defaults to 1 253# 254# Results: 255# items top count items from the queue; if there are not enough items 256# to fufill the request, throws an error. 257 258proc ::struct::prioqueue::_peek {name {count 1}} { 259 variable queues 260 if { $count < 1 } { 261 error "invalid item count $count" 262 } 263 264 if { $count > [llength $queues($name)] } { 265 error "insufficient items in prioqueue to fill request" 266 } 267 268 if { $count == 1 } { 269 # Handle this as a special case, so single item pops aren't listified 270 return [lindex [lindex $queues($name) 0] 1] 271 } 272 273 # Otherwise, return a list of items 274 set index [expr {$count - 1}] 275 foreach item [lrange $queues($name) 0 $index] { 276 lappend result [lindex $item 1] 277 } 278 return $result 279} 280 281# ::struct::prioqueue::_peekpriority -- 282# 283# Retrive the priority of an item on the queue without removing it. 284# 285# Arguments: 286# name name of the queue object. 287# count number of items to peek; defaults to 1 288# 289# Results: 290# items top count items from the queue; if there are not enough items 291# to fufill the request, throws an error. 292 293proc ::struct::prioqueue::_peekpriority {name {count 1}} { 294 variable queues 295 if { $count < 1 } { 296 error "invalid item count $count" 297 } 298 299 if { $count > [llength $queues($name)] } { 300 error "insufficient items in prioqueue to fill request" 301 } 302 303 if { $count == 1 } { 304 # Handle this as a special case, so single item pops aren't listified 305 return [lindex [lindex $queues($name) 0] 0] 306 } 307 308 # Otherwise, return a list of items 309 set index [expr {$count - 1}] 310 foreach item [lrange $queues($name) 0 $index] { 311 lappend result [lindex $item 0] 312 } 313 return $result 314} 315 316 317# ::struct::prioqueue::_put -- 318# 319# Put an item into a queue. 320# 321# Arguments: 322# name name of the queue object 323# args list of the form "item1 prio1 item2 prio2 item3 prio3" 324# 325# Results: 326# None. 327 328proc ::struct::prioqueue::_put {name args} { 329 variable queues 330 variable queues_sorting 331 variable sortopt 332 variable sortdir 333 334 if { [llength $args] == 0 || [llength $args] % 2} { 335 error "wrong # args: should be \"$name put item prio ?item prio ...?\"" 336 } 337 338 # check for prio type before adding 339 switch -exact -- $queues_sorting($name) { 340 0 { 341 foreach {item prio} $args { 342 if {![string is integer -strict $prio]} { 343 error "priority \"$prio\" is not an integer type value" 344 } 345 } 346 } 347 1 { 348 foreach {item prio} $args { 349 if {![string is double -strict $prio]} { 350 error "priority \"$prio\" is not a real type value" 351 } 352 } 353 } 354 default { 355 #no restrictions for -ascii and -dictionary 356 } 357 } 358 359 # sort by priorities 360 set opt [lindex $sortopt $queues_sorting($name)] 361 set dir [lindex $sortdir $queues_sorting($name)] 362 363 # add only if check has passed 364 foreach {item prio} $args { 365 set new [list $prio $item] 366 set queues($name) [__linsertsorted [K $queues($name) [set queues($name) ""]] $new $opt $dir] 367 } 368 return 369} 370 371# ::struct::prioqueue::_remove -- 372# 373# Delete an item together with it's related priority value from the queue. 374# 375# Arguments: 376# name name of the queue object 377# item item to be removed 378# 379# Results: 380# None. 381 382if {[package vcompare [package present Tcl] 8.5] < 0} { 383 # 8.4-: We have -index option for lsearch, so we use glob to allow 384 # us to create a pattern which can ignore the priority value. We 385 # quote everything in the item to prevent it from being 386 # glob-matched, exact matching is required. 387 388 proc ::struct::prioqueue::_remove {name item} { 389 variable queues 390 set queuelist $queues($name) 391 set itemrep "* \\[join [split $item {}] "\\"]" 392 set foundat [lsearch -glob $queuelist $itemrep] 393 394 # the item to remove was not found if foundat remains at -1, 395 # nothing to replace then 396 if {$foundat < 0} return 397 set queues($name) [lreplace $queuelist $foundat $foundat] 398 return 399 } 400} else { 401 # 8.5+: We have the -index option, allowing us to exactly address 402 # the column used to search. 403 404 proc ::struct::prioqueue::_remove {name item} { 405 variable queues 406 set queuelist $queues($name) 407 set foundat [lsearch -index 1 -exact $queuelist $item] 408 409 # the item to remove was not found if foundat remains at -1, 410 # nothing to replace then 411 if {$foundat < 0} return 412 set queues($name) [lreplace $queuelist $foundat $foundat] 413 return 414 } 415} 416 417# ::struct::prioqueue::_size -- 418# 419# Return the number of objects on a queue. 420# 421# Arguments: 422# name name of the queue object. 423# 424# Results: 425# count number of items on the queue. 426 427proc ::struct::prioqueue::_size {name} { 428 variable queues 429 return [llength $queues($name)] 430} 431 432# ::struct::prioqueue::__linsertsorted 433# 434# Helper proc for inserting into a sorted list. 435# 436# 437 438proc ::struct::prioqueue::__linsertsorted {list newElement sortopt sortdir} { 439 440 set cmpcmd __elementcompare${sortopt} 441 set pos -1 442 set newPrio [lindex $newElement 0] 443 444 # do a binary search 445 set lower -1 446 set upper [llength $list] 447 set bound [expr {$upper+1}] 448 set pivot 0 449 450 if {$upper > 0} { 451 while {$lower +1 != $upper } { 452 453 # get the pivot element 454 set pivot [expr {($lower + $upper) / 2}] 455 set element [lindex $list $pivot] 456 set prio [lindex $element 0] 457 458 # check 459 set test [$cmpcmd $prio $newPrio $sortdir] 460 if {$test == 0} { 461 set pos $pivot 462 set upper $pivot 463 # now break as we need the last item 464 break 465 } elseif {$test > 0 } { 466 # search lower section 467 set upper $pivot 468 set bound $upper 469 set pos -1 470 } else { 471 # search upper section 472 set lower $pivot 473 set pos $bound 474 } 475 } 476 477 478 if {$pos == -1} { 479 # we do an insert before the pivot element 480 set pos $pivot 481 } 482 483 # loop to the last matching element to 484 # keep a stable insertion order 485 while {[$cmpcmd $prio $newPrio $sortdir]==0} { 486 incr pos 487 if {$pos > [llength $list]} {break} 488 set element [lindex $list $pos] 489 set prio [lindex $element 0] 490 } 491 492 } else { 493 set pos 0 494 } 495 496 # do the insert without copying 497 linsert [K $list [set list ""]] $pos $newElement 498} 499 500# ::struct::prioqueue::__elementcompare 501# 502# Compare helpers with the sort options. 503# 504# 505 506proc ::struct::prioqueue::__elementcompare-integer {prio newPrio sortdir} { 507 return [expr {$prio < $newPrio ? -1*$sortdir : ($prio != $newPrio)*$sortdir}] 508} 509 510proc ::struct::prioqueue::__elementcompare-real {prio newPrio sortdir} { 511 return [expr {$prio < $newPrio ? -1*$sortdir : ($prio != $newPrio)*$sortdir}] 512} 513 514proc ::struct::prioqueue::__elementcompare-ascii {prio newPrio sortdir} { 515 return [expr {[string compare $prio $newPrio]*$sortdir}] 516} 517 518proc ::struct::prioqueue::__elementcompare-dictionary {prio newPrio sortdir} { 519 # need to use lsort to access -dictionary sorting 520 set tlist [lsort -increasing -dictionary [list $prio $newPrio]] 521 set e1 [string equal [lindex $tlist 0] $prio] 522 set e2 [string equal [lindex $tlist 1] $prio] 523 return [expr {$e1 > $e2 ? -1*$sortdir : ($e1 != $e2)*$sortdir}] 524} 525 526# ### ### ### ######### ######### ######### 527## Ready 528 529namespace eval ::struct { 530 # Get 'prioqueue::prioqueue' into the general structure namespace. 531 namespace import -force prioqueue::prioqueue 532 namespace export prioqueue 533} 534 535package provide struct::prioqueue 1.4 536