prioqueue.tcl

Go to the documentation of this file.
00001 /*  prioqueue.tcl --*/
00002 /* */
00003 /*   Priority Queue implementation for Tcl.*/
00004 /* */
00005 /*  adapted from queue.tcl*/
00006 /*  Copyright (c) 2002,2003 Michael Schlenker*/
00007 /* */
00008 /*  See the file "license.terms" for information on usage and redistribution*/
00009 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00010 /* */
00011 /*  RCS: @(#) $Id: prioqueue.tcl,v 1.9 2005/09/23 16:17:26 mic42 Exp $*/
00012 
00013 package require Tcl 8.2
00014 
00015 namespace ::struct {}
00016 
00017 namespace ::struct::prioqueue {
00018     /*  The queues array holds all of the queues you've made*/
00019     variable queues
00020 
00021     /*  counter is used to give a unique name for unnamed queues*/
00022     variable counter 0
00023 
00024     /*  commands is the list of subcommands recognized by the queue*/
00025     variable commands [list \
00026         "clear" \
00027         "destroy"   \
00028         "get"   \
00029         "peek"  \
00030         "put"   \
00031         "size"  \
00032         "peekpriority" \
00033         ]
00034 
00035     variable sortopt [list \
00036         "-integer" \
00037         "-real" \
00038         "-ascii" \
00039         "-dictionary" \
00040         ]
00041 
00042     /*  this is a simple design decision, that integer and real*/
00043     /*  are sorted decreasing (-1), and -ascii and -dictionary are sorted -increasing (1)*/
00044     /*  the values here map to the sortopt list*/
00045     /*  could be changed to something configurable.*/
00046     variable sortdir [list \
00047         "-1" \
00048         "-1" \
00049         "1" \
00050         "1" \
00051         ]
00052 
00053 
00054 
00055     /*  Only export one command, the one used to instantiate a new queue*/
00056     namespace export prioqueue
00057 
00058     ret  K (type x , type y) {set x} ;/*  DKF's K combinator*/
00059 }
00060 
00061 /*  ::struct::prioqueue::prioqueue --*/
00062 /* */
00063 /*    Create a new prioqueue with a given name; if no name is given, use*/
00064 /*    prioqueueX, where X is a number.*/
00065 /* */
00066 /*  Arguments:*/
00067 /*    sorting sorting option for lsort to use, no -command option*/
00068 /*            defaults to integer*/
00069 /*    name    name of the queue; if null, generate one.*/
00070 /*            names may not begin with -*/
00071 /* */
00072 /* */
00073 /*  Results:*/
00074 /*    name    name of the queue created*/
00075 
00076 ret  ::struct::prioqueue::prioqueue (type args) {
00077     variable queues
00078     variable counter
00079     variable queues_sorting
00080     variable sortopt
00081 
00082     # check args
00083     if {[llength $args] > 2} {
00084         error "wrong # args: should be \"[lindex [info level 0] 0] ?-ascii|-dictionary|-integer|-real? ?name?\""
00085     }
00086     if {[llength $args] == 0} {
00087         # defaulting to integer priorities
00088         set sorting -integer
00089     } else {
00090         if {[llength $args] == 1} {
00091             if {[string match "-*" [lindex $args 0]]==1} {
00092                 set sorting [lindex $args 0]
00093             } else {
00094                 set sorting -integer
00095                 set name [lindex $args 0]
00096             }
00097         } else {
00098             if {[llength $args] == 2} {
00099                 foreach {sorting name} $args {break}
00100             }
00101         }
00102     }
00103     # check option (like lsort sorting options without -command)
00104     if {[lsearch $sortopt $sorting] == -1} {
00105         # if sortoption is unknown, but name is a sortoption we give a better error message
00106         if {[info exists name] && [lsearch $sortopt $name]!=-1} {
00107             error "wrong argument position: should be \"[lindex [info level 0] 0] ?-ascii|-dictionary|-integer|-real? ?name?\""
00108         }
00109         error "unknown sort option \"$sorting\""
00110     }
00111     # create name if not given
00112     if {![info exists name]} {
00113         incr counter
00114         set name "prioqueue${counter}"
00115     }
00116 
00117     if { ![string equal [info commands ::$name] ""] } {
00118     error "command \"$name\" already exists, unable to create prioqueue"
00119     }
00120 
00121     # Initialize the queue as empty
00122     set queues($name) [list ]
00123     switch -exact -- $sorting {
00124     -integer { set queues_sorting($name) 0}
00125     -real    { set queues_sorting($name) 1}
00126     -ascii   { set queues_sorting($name) 2}
00127     -dictionary { set queues_sorting($name) 3}
00128     }
00129 
00130     # Create the command to manipulate the queue
00131     interp alias {} ::$name {} ::struct::prioqueue::QueueProc $name
00132 
00133     return $name
00134 }
00135 
00136 /* */
00137 /*  Private functions follow*/
00138 
00139 /*  ::struct::prioqueue::QueueProc --*/
00140 /* */
00141 /*    Command that processes all queue object commands.*/
00142 /* */
00143 /*  Arguments:*/
00144 /*    name    name of the queue object to manipulate.*/
00145 /*    args    command name and args for the command*/
00146 /* */
00147 /*  Results:*/
00148 /*    Varies based on command to perform*/
00149 
00150 ret  ::struct::prioqueue::QueueProc (type name , optional cmd ="" , type args) {
00151     # Do minimal args checks here
00152     if { [llength [info level 0]] == 2 } {
00153     error "wrong # args: should be \"$name option ?arg arg ...?\""
00154     }
00155 
00156     # Split the args into command and args components
00157     if { [string equal [info commands ::struct::prioqueue::_$cmd] ""] } {
00158     variable commands
00159     set optlist [join $commands ", "]
00160     set optlist [linsert $optlist "end-1" "or"]
00161     error "bad option \"$cmd\": must be $optlist"
00162     }
00163     return [eval [linsert $args 0 ::struct::prioqueue::_$cmd $name]]
00164 }
00165 
00166 /*  ::struct::prioqueue::_clear --*/
00167 /* */
00168 /*    Clear a queue.*/
00169 /* */
00170 /*  Arguments:*/
00171 /*    name    name of the queue object.*/
00172 /* */
00173 /*  Results:*/
00174 /*    None.*/
00175 
00176 ret  ::struct::prioqueue::_clear (type name) {
00177     variable queues
00178     set queues($name) [list]
00179     return
00180 }
00181 
00182 /*  ::struct::prioqueue::_destroy --*/
00183 /* */
00184 /*    Destroy a queue object by removing it's storage space and*/
00185 /*    eliminating it's proc.*/
00186 /* */
00187 /*  Arguments:*/
00188 /*    name    name of the queue object.*/
00189 /* */
00190 /*  Results:*/
00191 /*    None.*/
00192 
00193 ret  ::struct::prioqueue::_destroy (type name) {
00194     variable queues
00195     variable queues_sorting
00196     unset queues($name)
00197     unset queues_sorting($name)
00198     interp alias {} ::$name {}
00199     return
00200 }
00201 
00202 /*  ::struct::prioqueue::_get --*/
00203 /* */
00204 /*    Get an item from a queue.*/
00205 /* */
00206 /*  Arguments:*/
00207 /*    name    name of the queue object.*/
00208 /*    count   number of items to get; defaults to 1*/
00209 /* */
00210 /*  Results:*/
00211 /*    item    first count items from the queue; if there are not enough*/
00212 /*            items in the queue, throws an error.*/
00213 /* */
00214 
00215 ret  ::struct::prioqueue::_get (type name , optional count =1) {
00216     variable queues
00217     if { $count < 1 } {
00218     error "invalid item count $count"
00219     }
00220 
00221     if { $count > [llength $queues($name)] } {
00222     error "insufficient items in prioqueue to fill request"
00223     }
00224 
00225     if { $count == 1 } {
00226     # Handle this as a special case, so single item gets aren't listified
00227     set item [lindex [lindex $queues($name) 0] 1]
00228     set queues($name) [lreplace [K $queues($name) [set queues($name) ""]] 0 0]
00229     return $item
00230     }
00231 
00232     # Otherwise, return a list of items
00233     incr count -1
00234     set items [lrange $queues($name) 0 $count]
00235     foreach item $items {
00236         lappend result [lindex $item 1]
00237     }
00238     set items ""
00239 
00240     set queues($name) [lreplace [K $queues($name) [set queues($name) ""]] 0 $count]
00241     return $result
00242 }
00243 
00244 /*  ::struct::prioqueue::_peek --*/
00245 /* */
00246 /*    Retrive the value of an item on the queue without removing it.*/
00247 /* */
00248 /*  Arguments:*/
00249 /*    name    name of the queue object.*/
00250 /*    count   number of items to peek; defaults to 1*/
00251 /* */
00252 /*  Results:*/
00253 /*    items   top count items from the queue; if there are not enough items*/
00254 /*        to fufill the request, throws an error.*/
00255 
00256 ret  ::struct::prioqueue::_peek (type name , optional count =1) {
00257     variable queues
00258     if { $count < 1 } {
00259     error "invalid item count $count"
00260     }
00261 
00262     if { $count > [llength $queues($name)] } {
00263     error "insufficient items in prioqueue to fill request"
00264     }
00265 
00266     if { $count == 1 } {
00267     # Handle this as a special case, so single item pops aren't listified
00268     return [lindex [lindex $queues($name) 0] 1]
00269     }
00270 
00271     # Otherwise, return a list of items
00272     set index [expr {$count - 1}]
00273     foreach item [lrange $queues($name) 0 $index] {
00274         lappend result [lindex $item 1]
00275     }
00276     return $result
00277 }
00278 
00279 /*  ::struct::prioqueue::_peekpriority --*/
00280 /* */
00281 /*    Retrive the priority of an item on the queue without removing it.*/
00282 /* */
00283 /*  Arguments:*/
00284 /*    name    name of the queue object.*/
00285 /*    count   number of items to peek; defaults to 1*/
00286 /* */
00287 /*  Results:*/
00288 /*    items   top count items from the queue; if there are not enough items*/
00289 /*        to fufill the request, throws an error.*/
00290 
00291 ret  ::struct::prioqueue::_peekpriority (type name , optional count =1) {
00292     variable queues
00293     if { $count < 1 } {
00294     error "invalid item count $count"
00295     }
00296 
00297     if { $count > [llength $queues($name)] } {
00298     error "insufficient items in prioqueue to fill request"
00299     }
00300 
00301     if { $count == 1 } {
00302     # Handle this as a special case, so single item pops aren't listified
00303     return [lindex [lindex $queues($name) 0] 0]
00304     }
00305 
00306     # Otherwise, return a list of items
00307     set index [expr {$count - 1}]
00308     foreach item [lrange $queues($name) 0 $index] {
00309         lappend result [lindex $item 0]
00310     }
00311     return $result
00312 }
00313 
00314 
00315 /*  ::struct::prioqueue::_put --*/
00316 /* */
00317 /*    Put an item into a queue.*/
00318 /* */
00319 /*  Arguments:*/
00320 /*    name    name of the queue object*/
00321 /*    args    list of the form "item1 prio1 item2 prio2 item3 prio3"*/
00322 /* */
00323 /*  Results:*/
00324 /*    None.*/
00325 
00326 ret  ::struct::prioqueue::_put (type name , type args) {
00327     variable queues
00328     variable queues_sorting
00329     variable sortopt
00330     variable sortdir
00331 
00332     if { [llength $args] == 0 || [llength $args] % 2} {
00333     error "wrong # args: should be \"$name put item prio ?item prio ...?\""
00334     }
00335 
00336     # check for prio type before adding
00337     switch -exact -- $queues_sorting($name) {
00338         0    {
00339         foreach {item prio} $args {
00340         if {![string is integer -strict $prio]} {
00341             error "priority \"$prio\" is not an integer type value"
00342         }
00343         }
00344     }
00345         1    {
00346         foreach {item prio} $args {
00347         if {![string is double -strict $prio]} {
00348             error "priority \"$prio\" is not a real type value"
00349         }
00350         }
00351     }
00352         default {
00353         #no restrictions for -ascii and -dictionary
00354     }
00355     }
00356 
00357     # sort by priorities
00358     set opt [lindex $sortopt $queues_sorting($name)]
00359     set dir [lindex $sortdir $queues_sorting($name)]
00360 
00361     # add only if check has passed
00362     foreach {item prio} $args {
00363         set new [list $prio $item]
00364         set queues($name) [__linsertsorted [K $queues($name) [set queues($name) ""]] $new $opt $dir]
00365     }
00366     return
00367 }
00368 
00369 /*  ::struct::prioqueue::_size --*/
00370 /* */
00371 /*    Return the number of objects on a queue.*/
00372 /* */
00373 /*  Arguments:*/
00374 /*    name    name of the queue object.*/
00375 /* */
00376 /*  Results:*/
00377 /*    count   number of items on the queue.*/
00378 
00379 ret  ::struct::prioqueue::_size (type name) {
00380     variable queues
00381     return [llength $queues($name)]
00382 }
00383 
00384 /*  ::struct::prioqueue::__linsertsorted*/
00385 /* */
00386 /*  Helper proc for inserting into a sorted list.*/
00387 /* */
00388 /* */
00389 
00390 ret  ::struct::prioqueue::__linsertsorted (type list , type newElement , type sortopt , type sortdir) {
00391     
00392     set cmpcmd __elementcompare${sortopt}
00393     set pos -1
00394     set newPrio [lindex $newElement 0]
00395 
00396     # do a binary search
00397     set lower -1
00398     set upper [llength $list]
00399     set bound [expr {$upper+1}]
00400     set pivot 0
00401     
00402     if {$upper > 0} {
00403         while {$lower +1 != $upper } {
00404            
00405            # get the pivot element
00406            set pivot [expr {($lower + $upper) / 2}]
00407            set element [lindex $list $pivot]
00408         set prio [lindex $element 0]
00409            
00410            # check
00411            set test [$cmpcmd $prio $newPrio $sortdir]
00412            if {$test == 0} {
00413                 set pos $pivot
00414                 set upper $pivot
00415                 # now break as we need the last item
00416                 break
00417            } elseif {$test > 0 } {
00418                 # search lower section
00419                 set upper $pivot
00420                 set bound $upper
00421                 set pos -1
00422            } else {
00423                 # search upper section
00424                 set lower $pivot
00425                 set pos $bound
00426            }
00427         }
00428         
00429         
00430         if {$pos == -1} {
00431             # we do an insert before the pivot element
00432             set pos $pivot
00433         }
00434         
00435         # loop to the last matching element to 
00436         # keep a stable insertion order
00437         while {[$cmpcmd $prio $newPrio $sortdir]==0} {
00438         incr pos
00439             if {$pos > [llength $list]} {break}
00440             set element [lindex $list $pos]
00441             set prio [lindex $element 0]
00442         }            
00443         
00444     } else {
00445         set pos 0
00446     }
00447     
00448     # do the insert without copying
00449     linsert [K $list [set list ""]] $pos $newElement
00450 }
00451 
00452 /*  ::struct::prioqueue::__elementcompare*/
00453 /* */
00454 /*  Compare helpers with the sort options.*/
00455 /* */
00456 /* */
00457 
00458 ret  ::struct::prioqueue::__elementcompare-integer (type prio , type newPrio , type sortdir) {
00459     return [expr {$prio < $newPrio ? -1*$sortdir : ($prio != $newPrio)*$sortdir}]
00460 }
00461 
00462 ret  ::struct::prioqueue::__elementcompare-real (type prio , type newPrio , type sortdir) {
00463     return [expr {$prio < $newPrio ? -1*$sortdir : ($prio != $newPrio)*$sortdir}] 
00464 }
00465 
00466 ret  ::struct::prioqueue::__elementcompare-ascii (type prio , type newPrio , type sortdir) {
00467     return [expr {[string compare $prio $newPrio]*$sortdir}]
00468 }
00469 
00470 ret  ::struct::prioqueue::__elementcompare-dictionary (type prio , type newPrio , type sortdir) {
00471     # need to use lsort to access -dictionary sorting
00472     set tlist [lsort -increasing -dictionary [list $prio $newPrio]]
00473     set e1 [string equal [lindex $tlist 0]  $prio]
00474     set e2 [string equal [lindex $tlist 1]  $prio]    
00475     return [expr {$e1 > $e2 ? -1*$sortdir : ($e1 != $e2)*$sortdir}]
00476 }
00477 
00478 /*  ### ### ### ######### ######### #########*/
00479 /*  Ready*/
00480 
00481 namespace ::struct {
00482     /*  Get 'prioqueue::prioqueue' into the general structure namespace.*/
00483     namespace import -force prioqueue::prioqueue
00484     namespace export prioqueue
00485 }
00486 package provide struct::prioqueue 1.3.1
00487 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1