list.tcl

Go to the documentation of this file.
00001 /* ----------------------------------------------------------------------*/
00002 /* */
00003 /*  list.tcl --*/
00004 /* */
00005 /*  Definitions for extended processing of Tcl lists.*/
00006 /* */
00007 /*  Copyright (c) 2003 by Kevin B. Kenny.  All rights reserved.*/
00008 /* */
00009 /*  See the file "license.terms" for information on usage and redistribution*/
00010 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00011 /* */
00012 /*  RCS: @(#) $Id: list.tcl,v 1.22 2007/05/16 22:20:16 kennykb Exp $*/
00013 /* */
00014 /* ----------------------------------------------------------------------*/
00015 
00016 package require Tcl 8.0
00017 package require cmdline
00018 
00019 namespace ::struct { namespace list {} }
00020 
00021 namespace ::struct::list {
00022     namespace export list
00023 
00024     if {0} {
00025     /*  Possibly in the future.*/
00026     namespace export Lassign
00027     namespace export LdbJoin
00028     namespace export LdbJoinOuter
00029     namespace export Lequal
00030     namespace export Lfilter
00031     namespace export Lfilterfor
00032     namespace export Lfirstperm
00033     namespace export Lflatten
00034     namespace export Lfold
00035     namespace export Lforeachperm
00036     namespace export Liota
00037     namespace export LlcsInvert
00038     namespace export LlcsInvert2
00039     namespace export LlcsInvertMerge
00040     namespace export LlcsInvertMerge2
00041     namespace export LlongestCommonSubsequence
00042     namespace export LlongestCommonSubsequence2
00043     namespace export Lmap
00044     namespace export Lmapfor
00045     namespace export Lnextperm
00046     namespace export Lpermutations
00047     namespace export Lrepeat
00048     namespace export Lrepeatn
00049     namespace export Lreverse
00050     namespace export Lshift
00051     namespace export Lswap
00052     }
00053 }
00054 
00055 /* */
00056 /*  Public functions*/
00057 
00058 /*  ::struct::list::list --*/
00059 /* */
00060 /*  Command that access all list commands.*/
00061 /* */
00062 /*  Arguments:*/
00063 /*  cmd Name of the subcommand to dispatch to.*/
00064 /*  args    Arguments for the subcommand.*/
00065 /* */
00066 /*  Results:*/
00067 /*  Whatever the result of the subcommand is.*/
00068 
00069 ret  ::struct::list::list (type cmd , type args) {
00070     # Do minimal args checks here
00071     if { [llength [info level 0]] == 1 } {
00072     return -code error "wrong # args: should be \"$cmd ?arg arg ...?\""
00073     }
00074     set sub L$cmd
00075     if { [llength [info commands ::struct::list::$sub]] == 0 } {
00076     set optlist [info commands ::struct::list::L*]
00077     set xlist {}
00078     foreach p $optlist {
00079         lappend xlist [string range $p 1 end]
00080     }
00081     return -code error \
00082         "bad option \"$cmd\": must be [linsert [join $xlist ", "] "end-1" "or"]"
00083     }
00084     return [uplevel 1 [linsert $args 0 ::struct::list::$sub]]
00085 }
00086 
00087 /* */
00088 /*  Private functions follow*/
00089 /* */
00090 /*  Do a compatibility version of [lset] for pre-8.4 versions of Tcl.*/
00091 /*  This version does not do multi-arg [lset]!*/
00092 
00093 ret  ::struct::list::K ( type x , type y ) { set x }
00094 
00095 if { [package vcompare [package provide Tcl] 8.4] < 0 } {
00096     ret  ::struct::list::lset ( type var , type index , type arg ) {
00097     upvar 1 $var list
00098     set list [::lreplace [K $list [set list {}]] $index $index $arg]
00099     }
00100 }
00101 
00102 /* */
00103 /*  Implementations of the functionality.*/
00104 /* */
00105 
00106 /*  ::struct::list::LlongestCommonSubsequence --*/
00107 /* */
00108 /*        Computes the longest common subsequence of two lists.*/
00109 /* */
00110 /*  Parameters:*/
00111 /*        sequence1, sequence2 -- Two lists to compare.*/
00112 /*  maxOccurs -- If provided, causes the procedure to ignore*/
00113 /*           lines that appear more than $maxOccurs times*/
00114 /*           in the second sequence.  See below for a discussion.*/
00115 /*  Results:*/
00116 /*        Returns a list of two lists of equal length.*/
00117 /*        The first sublist is of indices into sequence1, and the*/
00118 /*        second sublist is of indices into sequence2.  Each corresponding*/
00119 /*        pair of indices corresponds to equal elements in the sequences;*/
00120 /*        the sequence returned is the longest possible.*/
00121 /* */
00122 /*  Side effects:*/
00123 /*        None.*/
00124 /* */
00125 /*  Notes:*/
00126 /* */
00127 /*  While this procedure is quite rapid for many tasks of file*/
00128 /*  comparison, its performance degrades severely if the second list*/
00129 /*  contains many equal elements (as, for instance, when using this*/
00130 /*  procedure to compare two files, a quarter of whose lines are blank.*/
00131 /*  This drawback is intrinsic to the algorithm used (see the References*/
00132 /*  for details).  One approach to dealing with this problem that is*/
00133 /*  sometimes effective in practice is arbitrarily to exclude elements*/
00134 /*  that appear more than a certain number of times.  This number is*/
00135 /*  provided as the 'maxOccurs' parameter.  If frequent lines are*/
00136 /*  excluded in this manner, they will not appear in the common subsequence*/
00137 /*  that is computed; the result will be the longest common subsequence*/
00138 /*  of infrequent elements.*/
00139 /* */
00140 /*  The procedure struct::list::LongestCommonSubsequence2*/
00141 /*  functions as a wrapper around this procedure; it computes the longest*/
00142 /*  common subsequence of infrequent elements, and then subdivides the*/
00143 /*  subsequences that lie between the matches to approximate the true*/
00144 /*  longest common subsequence.*/
00145 /* */
00146 /*  References:*/
00147 /*  J. W. Hunt and M. D. McIlroy, "An algorithm for differential*/
00148 /*  file comparison," Comp. Sci. Tech. Rep. #41, Bell Telephone*/
00149 /*  Laboratories (1976). Available on the Web at the second*/
00150 /*  author's personal site: http://www.cs.dartmouth.edu/~doug/*/
00151 
00152 ret  ::struct::list::LlongestCommonSubsequence (
00153     type sequence1
00154     , type sequence2
00155     , optional maxOccurs =0x7fffffff
00156 ) {
00157     # Construct a set of equivalence classes of lines in file 2
00158 
00159     set index 0
00160     foreach string $sequence2 {
00161     lappend eqv($string) $index
00162     incr index
00163     }
00164 
00165     # K holds descriptions of the common subsequences.
00166     # Initially, there is one common subsequence of length 0,
00167     # with a fence saying that it includes line -1 of both files.
00168     # The maximum subsequence length is 0; position 0 of
00169     # K holds a fence carrying the line following the end
00170     # of both files.
00171 
00172     lappend K [::list -1 -1 {}]
00173     lappend K [::list [llength $sequence1] [llength $sequence2] {}]
00174     set k 0
00175 
00176     # Walk through the first file, letting i be the index of the line and
00177     # string be the line itself.
00178 
00179     set i 0
00180     foreach string $sequence1 {
00181     # Consider each possible corresponding index j in the second file.
00182 
00183     if { [info exists eqv($string)]
00184          && [llength $eqv($string)] <= $maxOccurs } {
00185 
00186         # c is the candidate match most recently found, and r is the
00187         # length of the corresponding subsequence.
00188 
00189         set r 0
00190         set c [lindex $K 0]
00191 
00192         foreach j $eqv($string) {
00193         # Perform a binary search to find a candidate common
00194         # subsequence to which may be appended this match.
00195 
00196         set max $k
00197         set min $r
00198         set s [expr { $k + 1 }]
00199         while { $max >= $min } {
00200             set mid [expr { ( $max + $min ) / 2 }]
00201             set bmid [lindex [lindex $K $mid] 1]
00202             if { $j == $bmid } {
00203             break
00204             } elseif { $j < $bmid } {
00205             set max [expr {$mid - 1}]
00206             } else {
00207             set s $mid
00208             set min [expr { $mid + 1 }]
00209             }
00210         }
00211 
00212         # Go to the next match point if there is no suitable
00213         # candidate.
00214 
00215         if { $j == [lindex [lindex $K $mid] 1] || $s > $k} {
00216             continue
00217         }
00218 
00219         # s is the sequence length of the longest sequence
00220         # to which this match point may be appended. Make
00221         # a new candidate match and store the old one in K
00222         # Set r to the length of the new candidate match.
00223 
00224         set newc [::list $i $j [lindex $K $s]]
00225         if { $r >= 0 } {
00226             lset K $r $c
00227         }
00228         set c $newc
00229         set r [expr { $s + 1 }]
00230 
00231         # If we've extended the length of the longest match,
00232         # we're done; move the fence.
00233 
00234         if { $s >= $k } {
00235             lappend K [lindex $K end]
00236             incr k
00237             break
00238         }
00239         }
00240 
00241         # Put the last candidate into the array
00242 
00243         lset K $r $c
00244     }
00245 
00246     incr i
00247     }
00248 
00249     # Package the common subsequence in a convenient form
00250 
00251     set seta {}
00252     set setb {}
00253     set q [lindex $K $k]
00254 
00255     for { set i 0 } { $i < $k } {incr i } {
00256     lappend seta {}
00257     lappend setb {}
00258     }
00259     while { [lindex $q 0] >= 0 } {
00260     incr k -1
00261     lset seta $k [lindex $q 0]
00262     lset setb $k [lindex $q 1]
00263     set q [lindex $q 2]
00264     }
00265 
00266     return [::list $seta $setb]
00267 }
00268 
00269 /*  ::struct::list::LlongestCommonSubsequence2 --*/
00270 /* */
00271 /*  Derives an approximation to the longest common subsequence*/
00272 /*  of two lists.*/
00273 /* */
00274 /*  Parameters:*/
00275 /*  sequence1, sequence2 - Lists to be compared*/
00276 /*  maxOccurs - Parameter for imprecise matching - see below.*/
00277 /* */
00278 /*  Results:*/
00279 /*        Returns a list of two lists of equal length.*/
00280 /*        The first sublist is of indices into sequence1, and the*/
00281 /*        second sublist is of indices into sequence2.  Each corresponding*/
00282 /*        pair of indices corresponds to equal elements in the sequences;*/
00283 /*        the sequence returned is an approximation to the longest possible.*/
00284 /* */
00285 /*  Side effects:*/
00286 /*        None.*/
00287 /* */
00288 /*  Notes:*/
00289 /*  This procedure acts as a wrapper around the companion procedure*/
00290 /*  struct::list::LongestCommonSubsequence and accepts the same*/
00291 /*  parameters.  It first computes the longest common subsequence of*/
00292 /*  elements that occur no more than $maxOccurs times in the*/
00293 /*  second list.  Using that subsequence to align the two lists,*/
00294 /*  it then tries to augment the subsequence by computing the true*/
00295 /*  longest common subsequences of the sublists between matched pairs.*/
00296 
00297 ret  ::struct::list::LlongestCommonSubsequence2 (
00298     type sequence1
00299     , type sequence2
00300     , optional maxOccurs =0x7fffffff
00301 ) {
00302     # Derive the longest common subsequence of elements that occur at
00303     # most $maxOccurs times
00304 
00305     foreach { l1 l2 } \
00306     [LlongestCommonSubsequence $sequence1 $sequence2 $maxOccurs] {
00307         break
00308     }
00309 
00310     # Walk through the match points in the sequence just derived.
00311 
00312     set result1 {}
00313     set result2 {}
00314     set n1 0
00315     set n2 0
00316     foreach i1 $l1 i2 $l2 {
00317     if { $i1 != $n1 && $i2 != $n2 } {
00318         # The match points indicate that there are unmatched
00319         # elements lying between them in both input sequences.
00320         # Extract the unmatched elements and perform precise
00321         # longest-common-subsequence analysis on them.
00322 
00323         set subl1 [lrange $sequence1 $n1 [expr { $i1 - 1 }]]
00324         set subl2 [lrange $sequence2 $n2 [expr { $i2 - 1 }]]
00325         foreach { m1 m2 } [LlongestCommonSubsequence $subl1 $subl2] break
00326         foreach j1 $m1 j2 $m2 {
00327         lappend result1 [expr { $j1 + $n1 }]
00328         lappend result2 [expr { $j2 + $n2 }]
00329         }
00330     }
00331 
00332     # Add the current match point to the result
00333 
00334     lappend result1 $i1
00335     lappend result2 $i2
00336     set n1 [expr { $i1 + 1 }]
00337     set n2 [expr { $i2 + 1 }]
00338     }
00339 
00340     # If there are unmatched elements after the last match in both files,
00341     # perform precise longest-common-subsequence matching on them and
00342     # add the result to our return.
00343 
00344     if { $n1 < [llength $sequence1] && $n2 < [llength $sequence2] } {
00345     set subl1 [lrange $sequence1 $n1 end]
00346     set subl2 [lrange $sequence2 $n2 end]
00347     foreach { m1 m2 } [LlongestCommonSubsequence $subl1 $subl2] break
00348     foreach j1 $m1 j2 $m2 {
00349         lappend result1 [expr { $j1 + $n1 }]
00350         lappend result2 [expr { $j2 + $n2 }]
00351     }
00352     }
00353 
00354     return [::list $result1 $result2]
00355 }
00356 
00357 /*  ::struct::list::LlcsInvert --*/
00358 /* */
00359 /*  Takes the data describing a longest common subsequence of two*/
00360 /*  lists and inverts the information in the sense that the result*/
00361 /*  of this command will describe the differences between the two*/
00362 /*  sequences instead of the identical parts.*/
00363 /* */
00364 /*  Parameters:*/
00365 /*  lcsData     longest common subsequence of two lists as*/
00366 /*          returned by longestCommonSubsequence(2).*/
00367 /*  Results:*/
00368 /*  Returns a single list whose elements describe the differences*/
00369 /*  between the original two sequences. Each element describes*/
00370 /*  one difference through three pieces, the type of the change,*/
00371 /*  a pair of indices in the first sequence and a pair of indices*/
00372 /*  into the second sequence, in this order.*/
00373 /* */
00374 /*  Side effects:*/
00375 /*        None.*/
00376 
00377 ret  ::struct::list::LlcsInvert (type lcsData , type len1 , type len2) {
00378     return [LlcsInvert2 [::lindex $lcsData 0] [::lindex $lcsData 1] $len1 $len2]
00379 }
00380 
00381 ret  ::struct::list::LlcsInvert2 (type idx1 , type idx2 , type len1 , type len2) {
00382     set result {}
00383     set last1 -1
00384     set last2 -1
00385 
00386     foreach a $idx1 b $idx2 {
00387     # Four possible cases.
00388     # a) last1 ... a and last2 ... b are not empty.
00389     #    This is a 'change'.
00390     # b) last1 ... a is empty, last2 ... b is not.
00391     #    This is an 'addition'.
00392     # c) last1 ... a is not empty, last2 ... b is empty.
00393     #    This is a deletion.
00394     # d) If both ranges are empty we can ignore the
00395     #    two current indices.
00396 
00397     set empty1 [expr {($a - $last1) <= 1}]
00398     set empty2 [expr {($b - $last2) <= 1}]
00399 
00400     if {$empty1 && $empty2} {
00401         # Case (d), ignore the indices
00402     } elseif {$empty1} {
00403         # Case (b), 'addition'.
00404         incr last2 ; incr b -1
00405         lappend result [::list added [::list $last1 $a] [::list $last2 $b]]
00406         incr b
00407     } elseif {$empty2} {
00408         # Case (c), 'deletion'
00409         incr last1 ; incr a -1
00410         lappend result [::list deleted [::list $last1 $a] [::list $last2 $b]]
00411         incr a
00412     } else {
00413         # Case (q), 'change'.
00414         incr last1 ; incr a -1
00415         incr last2 ; incr b -1
00416         lappend result [::list changed [::list $last1 $a] [::list $last2 $b]]
00417         incr a
00418         incr b
00419     }
00420 
00421     set last1 $a
00422     set last2 $b
00423     }
00424 
00425     # Handle the last chunk, using the information about the length of
00426     # the original sequences.
00427 
00428     set empty1 [expr {($len1 - $last1) <= 1}]
00429     set empty2 [expr {($len2 - $last2) <= 1}]
00430 
00431     if {$empty1 && $empty2} {
00432     # Case (d), ignore the indices
00433     } elseif {$empty1} {
00434     # Case (b), 'addition'.
00435     incr last2 ; incr len2 -1
00436     lappend result [::list added [::list $last1 $len1] [::list $last2 $len2]]
00437     } elseif {$empty2} {
00438     # Case (c), 'deletion'
00439     incr last1 ; incr len1 -1
00440     lappend result [::list deleted [::list $last1 $len1] [::list $last2 $len2]]
00441     } else {
00442     # Case (q), 'change'.
00443     incr last1 ; incr len1 -1
00444     incr last2 ; incr len2 -1
00445     lappend result [::list changed [::list $last1 $len1] [::list $last2 $len2]]
00446     }
00447 
00448     return $result
00449 }
00450 
00451 ret  ::struct::list::LlcsInvertMerge (type lcsData , type len1 , type len2) {
00452     return [LlcsInvertMerge2 [::lindex $lcsData 0] [::lindex $lcsData 1] $len1 $len2]
00453 }
00454 
00455 ret  ::struct::list::LlcsInvertMerge2 (type idx1 , type idx2 , type len1 , type len2) {
00456     set result {}
00457     set last1 -1
00458     set last2 -1
00459 
00460     foreach a $idx1 b $idx2 {
00461     # Four possible cases.
00462     # a) last1 ... a and last2 ... b are not empty.
00463     #    This is a 'change'.
00464     # b) last1 ... a is empty, last2 ... b is not.
00465     #    This is an 'addition'.
00466     # c) last1 ... a is not empty, last2 ... b is empty.
00467     #    This is a deletion.
00468     # d) If both ranges are empty we can ignore the
00469     #    two current indices. For merging we simply
00470     #    take the information from the input.
00471 
00472     set empty1 [expr {($a - $last1) <= 1}]
00473     set empty2 [expr {($b - $last2) <= 1}]
00474 
00475     if {$empty1 && $empty2} {
00476         # Case (d), add 'unchanged' chunk.
00477         set type --
00478         foreach {type left right} [lindex $result end] break
00479         if {[string match unchanged $type]} {
00480         # There is an existing result to extend
00481         lset left end $a
00482         lset right end $b
00483         lset result end [::list unchanged $left $right]
00484         } else {
00485         # There is an unchanged result at the start of the list;
00486         # it may be extended.
00487         lappend result [::list unchanged [::list $a $a] [::list $b $b]]
00488         }
00489     } else {
00490         if {$empty1} {
00491         # Case (b), 'addition'.
00492         incr last2 ; incr b -1
00493         lappend result [::list added [::list $last1 $a] [::list $last2 $b]]
00494         incr b
00495         } elseif {$empty2} {
00496         # Case (c), 'deletion'
00497         incr last1 ; incr a -1
00498         lappend result [::list deleted [::list $last1 $a] [::list $last2 $b]]
00499         incr a
00500         } else {
00501         # Case (a), 'change'.
00502         incr last1 ; incr a -1
00503         incr last2 ; incr b -1
00504         lappend result [::list changed [::list $last1 $a] [::list $last2 $b]]
00505         incr a
00506         incr b
00507         }
00508         # Finally, the two matching lines are a new unchanged region
00509         lappend result [::list unchanged [::list $a $a] [::list $b $b]]
00510     }
00511     set last1 $a
00512     set last2 $b
00513     }
00514 
00515     # Handle the last chunk, using the information about the length of
00516     # the original sequences.
00517 
00518     set empty1 [expr {($len1 - $last1) <= 1}]
00519     set empty2 [expr {($len2 - $last2) <= 1}]
00520 
00521     if {$empty1 && $empty2} {
00522     # Case (d), ignore the indices
00523     } elseif {$empty1} {
00524     # Case (b), 'addition'.
00525     incr last2 ; incr len2 -1
00526     lappend result [::list added [::list $last1 $len1] [::list $last2 $len2]]
00527     } elseif {$empty2} {
00528     # Case (c), 'deletion'
00529     incr last1 ; incr len1 -1
00530     lappend result [::list deleted [::list $last1 $len1] [::list $last2 $len2]]
00531     } else {
00532     # Case (q), 'change'.
00533     incr last1 ; incr len1 -1
00534     incr last2 ; incr len2 -1
00535     lappend result [::list changed [::list $last1 $len1] [::list $last2 $len2]]
00536     }
00537 
00538     return $result
00539 }
00540 
00541 /*  ::struct::list::Lreverse --*/
00542 /* */
00543 /*  Reverses the contents of the list and returns the reversed*/
00544 /*  list as the result of the command.*/
00545 /* */
00546 /*  Parameters:*/
00547 /*  sequence    List to be reversed.*/
00548 /* */
00549 /*  Results:*/
00550 /*  The sequence in reverse.*/
00551 /* */
00552 /*  Side effects:*/
00553 /*        None.*/
00554 
00555 ret  ::struct::list::Lreverse (type sequence) {
00556     set l [::llength $sequence]
00557 
00558     # Shortcut for lists where reversing yields the list itself
00559     if {$l < 2} {return $sequence}
00560 
00561     # Perform true reversal
00562     set res [::list]
00563     while {$l} {
00564     ::lappend res [::lindex $sequence [incr l -1]]
00565     }
00566     return $res
00567 }
00568 
00569 
00570 /*  ::struct::list::Lassign --*/
00571 /* */
00572 /*  Assign list elements to variables.*/
00573 /* */
00574 /*  Parameters:*/
00575 /*  sequence    List to assign*/
00576 /*  args        Names of the variables to assign to.*/
00577 /* */
00578 /*  Results:*/
00579 /*  The unassigned part of the sequence. Can be empty.*/
00580 /* */
00581 /*  Side effects:*/
00582 /*        None.*/
00583 
00584 /*  Do a compatibility version of [assign] for pre-8.5 versions of Tcl.*/
00585 
00586 if { [package vcompare [package provide Tcl] 8.5] < 0 } {
00587 
00588     ret  ::struct::list::Lassign (type sequence , type v , type args) {
00589     set args [linsert $args 0 $v]
00590     set a [::llength $args]
00591 
00592     # Nothing to assign.
00593     #if {$a == 0} {return $sequence}
00594 
00595     # Perform assignments
00596     set i 0
00597     foreach v $args {
00598         upvar 1 $v var
00599         set      var [::lindex $sequence $i]
00600         incr i
00601     }
00602 
00603     # Return remainder, if there is any.
00604     return [::lrange $sequence $a end]
00605 }
00606 
00607 } else {
00608     /*  For 8.5 simply redirect the method to the core command.*/
00609 
00610     interp alias {} ::struct::list::Lassign {} lassign
00611 }
00612 
00613 
00614 /*  ::struct::list::Lshift --*/
00615 /* */
00616 /*  Shift a list in a variable one element down, and return first element*/
00617 /* */
00618 /*  Parameters:*/
00619 /*  listvar     Name of variable containing the list to shift.*/
00620 /* */
00621 /*  Results:*/
00622 /*  The first element of the list.*/
00623 /* */
00624 /*  Side effects:*/
00625 /*        After the call the list variable will contain*/
00626 /*  the second to last elements of the list.*/
00627 
00628 ret  ::struct::list::Lshift (type listvar) {
00629     upvar 1 $listvar list
00630     set list [Lassign [K $list [set list {}]] v]
00631     return $v
00632 }
00633 
00634 
00635 /*  ::struct::list::Lflatten --*/
00636 /* */
00637 /*  Remove nesting from the input*/
00638 /* */
00639 /*  Parameters:*/
00640 /*  sequence    List to flatten*/
00641 /* */
00642 /*  Results:*/
00643 /*  The input list with one or all levels of nesting removed.*/
00644 /* */
00645 /*  Side effects:*/
00646 /*        None.*/
00647 
00648 ret  ::struct::list::Lflatten (type args) {
00649     if {[::llength $args] < 1} {
00650     return -code error \
00651         "wrong#args: should be \"::struct::list::Lflatten ?-full? ?--? sequence\""
00652     }
00653 
00654     set full 0
00655     while {[string match -* [set opt [::lindex $args 0]]]} {
00656     switch -glob -- $opt {
00657         -full   {set full 1}
00658         --      {break}
00659         default {
00660         return -code error "Unknown option \"$opt\", should be either -full, or --"
00661         }
00662     }
00663     set args [::lrange $args 1 end]
00664     }
00665 
00666     if {[::llength $args] != 1} {
00667     return -code error \
00668         "wrong#args: should be \"::struct::list::Lflatten ?-full? ?--? sequence\""
00669     }
00670 
00671     set sequence [::lindex $args 0]
00672     set cont 1
00673     while {$cont} {
00674     set cont 0
00675     set result [::list]
00676     foreach item $sequence {
00677         # catch/llength detects if the item is following the list
00678         # syntax.
00679 
00680         if {[catch {llength $item} len]} {
00681         # Element is not a list in itself, no flatten, add it
00682         # as is.
00683         lappend result $item
00684         } else {
00685         # Element is parseable as list, add all sub-elements
00686         # to the result.
00687         foreach e $item {
00688             lappend result $e
00689         }
00690         }
00691     }
00692     if {$full && [string compare $sequence $result]} {set cont 1}
00693     set sequence $result
00694     }
00695     return $result
00696 }
00697 
00698 
00699 /*  ::struct::list::Lmap --*/
00700 /* */
00701 /*  Apply command to each element of a list and return concatenated results.*/
00702 /* */
00703 /*  Parameters:*/
00704 /*  sequence    List to operate on*/
00705 /*  cmdprefix   Operation to perform on the elements.*/
00706 /* */
00707 /*  Results:*/
00708 /*  List containing the result of applying cmdprefix to the elements of the*/
00709 /*  sequence.*/
00710 /* */
00711 /*  Side effects:*/
00712 /*        None of its own, but the command prefix can perform arbitry actions.*/
00713 
00714 ret  ::struct::list::Lmap (type sequence , type cmdprefix) {
00715     # Shortcut when nothing is to be done.
00716     if {[::llength $sequence] == 0} {return $sequence}
00717 
00718     set res [::list]
00719     foreach item $sequence {
00720     lappend res [uplevel 1 [linsert $cmdprefix end $item]]
00721     }
00722     return $res
00723 }
00724 
00725 /*  ::struct::list::Lmapfor --*/
00726 /* */
00727 /*  Apply a script to each element of a list and return concatenated results.*/
00728 /* */
00729 /*  Parameters:*/
00730 /*  sequence    List to operate on*/
00731 /*  script      The script to run on the elements.*/
00732 /* */
00733 /*  Results:*/
00734 /*  List containing the result of running script on the elements of the*/
00735 /*  sequence.*/
00736 /* */
00737 /*  Side effects:*/
00738 /*        None of its own, but the script can perform arbitry actions.*/
00739 
00740 ret  ::struct::list::Lmapfor (type var , type sequence , type script) {
00741     # Shortcut when nothing is to be done.
00742     if {[::llength $sequence] == 0} {return $sequence}
00743     upvar 1 $var item
00744 
00745     set res [::list]
00746     foreach item $sequence {
00747     lappend res [uplevel 1 $script]
00748     }
00749     return $res
00750 }
00751 
00752 /*  ::struct::list::Lfilter --*/
00753 /* */
00754 /*  Apply command to each element of a list and return elements passing the test.*/
00755 /* */
00756 /*  Parameters:*/
00757 /*  sequence    List to operate on*/
00758 /*  cmdprefix   Test to perform on the elements.*/
00759 /* */
00760 /*  Results:*/
00761 /*  List containing the elements of the input passing the test command.*/
00762 /* */
00763 /*  Side effects:*/
00764 /*        None of its own, but the command prefix can perform arbitrary actions.*/
00765 
00766 ret  ::struct::list::Lfilter (type sequence , type cmdprefix) {
00767     # Shortcut when nothing is to be done.
00768     if {[::llength $sequence] == 0} {return $sequence}
00769     return [Lfold $sequence {} [::list ::struct::list::FTest $cmdprefix]]
00770 }
00771 
00772 ret  ::struct::list::FTest (type cmdprefix , type result , type item) {
00773     set pass [uplevel 1 [::linsert $cmdprefix end $item]]
00774     if {$pass} {::lappend result $item}
00775     return $result
00776 }
00777 
00778 /*  ::struct::list::Lfilterfor --*/
00779 /* */
00780 /*  Apply expr condition to each element of a list and return elements passing the test.*/
00781 /* */
00782 /*  Parameters:*/
00783 /*  sequence    List to operate on*/
00784 /*  expr        Test to perform on the elements.*/
00785 /* */
00786 /*  Results:*/
00787 /*  List containing the elements of the input passing the test expression.*/
00788 /* */
00789 /*  Side effects:*/
00790 /*        None of its own, but the command prefix can perform arbitrary actions.*/
00791 
00792 ret  ::struct::list::Lfilterfor (type var , type sequence , type expr) {
00793     # Shortcut when nothing is to be done.
00794     if {[::llength $sequence] == 0} {return $sequence}
00795 
00796     upvar 1 $var item
00797     set result {}
00798     foreach item $sequence {
00799     if {[uplevel 1 [::list ::expr $expr]]} {
00800         lappend result $item
00801     }
00802     }
00803     return $result
00804 }
00805 
00806 /*  ::struct::list::Lsplit --*/
00807 /* */
00808 /*  Apply command to each element of a list and return elements passing*/
00809 /*  and failing the test. Basic idea by Salvatore Sanfilippo*/
00810 /*  (http://wiki.tcl.tk/lsplit). The implementation here is mine (AK),*/
00811 /*  and the interface is slightly different (Command prefix with the*/
00812 /*  list element given to it as argument vs. variable + script).*/
00813 /* */
00814 /*  Parameters:*/
00815 /*  sequence    List to operate on*/
00816 /*  cmdprefix   Test to perform on the elements.*/
00817 /*  args = empty | (varPass varFail)*/
00818 /* */
00819 /*  Results:*/
00820 /*  If the variables are specified then a list containing the*/
00821 /*  numbers of passing and failing elements, in this*/
00822 /*  order. Otherwise a list having two elements, the lists of*/
00823 /*  passing and failing elements, in this order.*/
00824 /* */
00825 /*  Side effects:*/
00826 /*        None of its own, but the command prefix can perform arbitrary actions.*/
00827 
00828 ret  ::struct::list::Lsplit (type sequence , type cmdprefix , type args) {
00829     set largs [::llength $args]
00830     if {$largs == 0} {
00831     # Shortcut when nothing is to be done.
00832     if {[::llength $sequence] == 0} {return {{} {}}}
00833     return [Lfold $sequence {} [::list ::struct::list::PFTest $cmdprefix]]
00834     } elseif {$largs == 2} {
00835     # Shortcut when nothing is to be done.
00836     foreach {pv fv} $args break
00837     upvar 1 $pv pass $fv fail
00838     if {[::llength $sequence] == 0} {
00839         set pass {}
00840         set fail {}
00841         return {0 0}
00842     }
00843     foreach {pass fail} [Lfold $sequence {} [::list ::struct::list::PFTest $cmdprefix]] break
00844     return [::list [llength $pass] [llength $fail]]
00845     } else {
00846     return -code error \
00847         "wrong#args: should be \"::struct::list::Lsplit sequence cmdprefix ?passVar failVar?"
00848     }
00849 }
00850 
00851 ret  ::struct::list::PFTest (type cmdprefix , type result , type item) {
00852     set passing [uplevel 1 [::linsert $cmdprefix end $item]]
00853     set pass {} ; set fail {}
00854     foreach {pass fail} $result break
00855     if {$passing} {
00856     ::lappend pass $item
00857     } else {
00858     ::lappend fail $item
00859     }
00860     return [::list $pass $fail]
00861 }
00862 
00863 /*  ::struct::list::Lfold --*/
00864 /* */
00865 /*  Fold list into one value.*/
00866 /* */
00867 /*  Parameters:*/
00868 /*  sequence    List to operate on*/
00869 /*  cmdprefix   Operation to perform on the elements.*/
00870 /* */
00871 /*  Results:*/
00872 /*  Result of applying cmdprefix to the elements of the*/
00873 /*  sequence.*/
00874 /* */
00875 /*  Side effects:*/
00876 /*        None of its own, but the command prefix can perform arbitry actions.*/
00877 
00878 ret  ::struct::list::Lfold (type sequence , type initialvalue , type cmdprefix) {
00879     # Shortcut when nothing is to be done.
00880     if {[::llength $sequence] == 0} {return $initialvalue}
00881 
00882     set res $initialvalue
00883     foreach item $sequence {
00884     set res [uplevel 1 [linsert $cmdprefix end $res $item]]
00885     }
00886     return $res
00887 }
00888 
00889 /*  ::struct::list::Liota --*/
00890 /* */
00891 /*  Return a list containing the integer numbers 0 ... n-1*/
00892 /* */
00893 /*  Parameters:*/
00894 /*  n   First number not in the generated list.*/
00895 /* */
00896 /*  Results:*/
00897 /*  A list containing integer numbers.*/
00898 /* */
00899 /*  Side effects:*/
00900 /*        None*/
00901 
00902 ret  ::struct::list::Liota (type n) {
00903     set retval [::list]
00904     for {set i 0} {$i < $n} {incr i} {
00905     ::lappend retval $i
00906     }
00907     return $retval
00908 }
00909 
00910 /*  ::struct::list::Lequal --*/
00911 /* */
00912 /*  Compares two lists for equality*/
00913 /*  (Same length, Same elements in same order).*/
00914 /* */
00915 /*  Parameters:*/
00916 /*  a   First list to compare.*/
00917 /*  b   Second list to compare.*/
00918 /* */
00919 /*  Results:*/
00920 /*  A boolean. True if the lists are equal.*/
00921 /* */
00922 /*  Side effects:*/
00923 /*        None*/
00924 
00925 ret  ::struct::list::Lequal (type a , type b) {
00926     # Author of this command is "Richard Suchenwirth"
00927 
00928     if {[::llength $a] != [::llength $b]} {return 0}
00929     if {[::lindex $a 0] == $a} {return [string equal $a $b]}
00930     foreach i $a j $b {if {![Lequal $i $j]} {return 0}}
00931     return 1
00932 }
00933 
00934 /*  ::struct::list::Lrepeatn --*/
00935 /* */
00936 /*  Create a list repeating the same value over again.*/
00937 /* */
00938 /*  Parameters:*/
00939 /*  value   value to use in the created list.*/
00940 /*  args    Dimension(s) of the (nested) list to create.*/
00941 /* */
00942 /*  Results:*/
00943 /*  A list*/
00944 /* */
00945 /*  Side effects:*/
00946 /*        None*/
00947 
00948 ret  ::struct::list::Lrepeatn (type value , type args) {
00949     if {[::llength $args] == 1} {set args [::lindex $args 0]}
00950     set buf {}
00951     foreach number $args {
00952     incr number 0 ;# force integer (1)
00953     set buf {}
00954     for {set i 0} {$i<$number} {incr i} {
00955         ::lappend buf $value
00956     }
00957     set value $buf
00958     }
00959     return $buf
00960     # (1): See 'Stress testing' (wiki) for why this makes the code safer.
00961 }
00962 
00963 /*  ::struct::list::Lrepeat --*/
00964 /* */
00965 /*  Create a list repeating the same value over again.*/
00966 /*  [Identical to the Tcl 8.5 lrepeat command]*/
00967 /* */
00968 /*  Parameters:*/
00969 /*  n   Number of replications.*/
00970 /*  args    values to use in the created list.*/
00971 /* */
00972 /*  Results:*/
00973 /*  A list*/
00974 /* */
00975 /*  Side effects:*/
00976 /*        None*/
00977 
00978 /*  Do a compatibility version of [repeat] for pre-8.5 versions of Tcl.*/
00979 
00980 if { [package vcompare [package provide Tcl] 8.5] < 0 } {
00981 
00982     ret  ::struct::list::Lrepeat (type positiveCount , type value , type args) {
00983     if {![string is integer -strict $positiveCount]} {
00984         return -code error "expected integer but got \"$positiveCount\""
00985     } elseif {$positiveCount < 1} {
00986         return -code error {must have a count of at least 1}
00987     }
00988 
00989     set args   [linsert $args 0 $value]
00990 
00991     if {$positiveCount == 1} {
00992         # Tcl itself has already listified the incoming parameters
00993         # via 'args'.
00994         return $args
00995     }
00996 
00997     set result [::list]
00998     while {$positiveCount > 0} {
00999         if {($positiveCount % 2) == 0} {
01000         set args [concat $args $args]
01001         set positiveCount [expr {$positiveCount/2}]
01002         } else {
01003         set result [concat $result $args]
01004         incr positiveCount -1
01005         }
01006     }
01007     return $result
01008     }
01009 
01010 } else {
01011     /*  For 8.5 simply redirect the method to the core command.*/
01012 
01013     interp alias {} ::struct::list::Lrepeat {} lrepeat
01014 }
01015 
01016 /*  ::struct::list::LdbJoin(Keyed) --*/
01017 /* */
01018 /*  Relational table joins.*/
01019 /* */
01020 /*  Parameters:*/
01021 /*  args    key specs and tables to join*/
01022 /* */
01023 /*  Results:*/
01024 /*  A table/matrix as nested list. See*/
01025 /*  struct/matrix set/get rect for structure.*/
01026 /* */
01027 /*  Side effects:*/
01028 /*        None*/
01029 
01030 ret  ::struct::list::LdbJoin (type args) {
01031     # --------------------------------
01032     # Process options ...
01033 
01034     set mode   inner
01035     set keyvar {}
01036 
01037     while {[llength $args]} {
01038         set err [::cmdline::getopt args {inner left right full keys.arg} opt arg]
01039     if {$err == 1} {
01040         if {[string equal $opt keys]} {
01041         set keyvar $arg
01042         } else {
01043         set mode $opt
01044         }
01045     } elseif {$err < 0} {
01046         return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? ?-keys varname? \{key table\}..."
01047     } else {
01048         # Non-option argument found, stop processing.
01049         break
01050     }
01051     }
01052 
01053     set inner       [string equal $mode inner]
01054     set innerorleft [expr {$inner || [string equal $mode left]}]
01055 
01056     # --------------------------------
01057     # Process tables ...
01058 
01059     if {([llength $args] % 2) != 0} {
01060     return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? \{key table\}..."
01061     }
01062 
01063     # One table only, join is identity
01064     if {[llength $args] == 2} {return [lindex $args 1]}
01065 
01066     # Use first table for setup.
01067 
01068     foreach {key table} $args break
01069 
01070     # Check for possible early abort
01071     if {$innerorleft && ([llength $table] == 0)} {return {}}
01072 
01073     set width 0
01074     array set state {}
01075 
01076     set keylist [InitMap state width $key $table]
01077 
01078     # Extend state with the remaining tables.
01079 
01080     foreach {key table} [lrange $args 2 end] {
01081     # Check for possible early abort
01082     if {$inner && ([llength $table] == 0)} {return {}}
01083 
01084     switch -exact -- $mode {
01085         inner {set keylist [MapExtendInner      state       $key $table]}
01086         left  {set keylist [MapExtendLeftOuter  state width $key $table]}
01087         right {set keylist [MapExtendRightOuter state width $key $table]}
01088         full  {set keylist [MapExtendFullOuter  state width $key $table]}
01089     }
01090 
01091     # Check for possible early abort
01092     if {$inner && ([llength $keylist] == 0)} {return {}}
01093     }
01094 
01095     if {[string length $keyvar]} {
01096     upvar 1 $keyvar keys
01097     set             keys $keylist
01098     }
01099 
01100     return [MapToTable state $keylist]
01101 }
01102 
01103 ret  ::struct::list::LdbJoinKeyed (type args) {
01104     # --------------------------------
01105     # Process options ...
01106 
01107     set mode   inner
01108     set keyvar {}
01109 
01110     while {[llength $args]} {
01111         set err [::cmdline::getopt args {inner left right full keys.arg} opt arg]
01112     if {$err == 1} {
01113         if {[string equal $opt keys]} {
01114         set keyvar $arg
01115         } else {
01116         set mode $opt
01117         }
01118     } elseif {$err < 0} {
01119         return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? table..."
01120     } else {
01121         # Non-option argument found, stop processing.
01122         break
01123     }
01124     }
01125 
01126     set inner       [string equal $mode inner]
01127     set innerorleft [expr {$inner || [string equal $mode left]}]
01128 
01129     # --------------------------------
01130     # Process tables ...
01131 
01132     # One table only, join is identity
01133     if {[llength $args] == 1} {
01134     return [Dekey [lindex $args 0]]
01135     }
01136 
01137     # Use first table for setup.
01138 
01139     set table [lindex $args 0]
01140 
01141     # Check for possible early abort
01142     if {$innerorleft && ([llength $table] == 0)} {return {}}
01143 
01144     set width 0
01145     array set state {}
01146 
01147     set keylist [InitKeyedMap state width $table]
01148 
01149     # Extend state with the remaining tables.
01150 
01151     foreach table [lrange $args 1 end] {
01152     # Check for possible early abort
01153     if {$inner && ([llength $table] == 0)} {return {}}
01154 
01155     switch -exact -- $mode {
01156         inner {set keylist [MapKeyedExtendInner      state       $table]}
01157         left  {set keylist [MapKeyedExtendLeftOuter  state width $table]}
01158         right {set keylist [MapKeyedExtendRightOuter state width $table]}
01159         full  {set keylist [MapKeyedExtendFullOuter  state width $table]}
01160     }
01161 
01162     # Check for possible early abort
01163     if {$inner && ([llength $keylist] == 0)} {return {}}
01164     }
01165 
01166     if {[string length $keyvar]} {
01167     upvar 1 $keyvar keys
01168     set             keys $keylist
01169     }
01170 
01171     return [MapToTable state $keylist]
01172 }
01173 
01174 /*  Helpers for the relational joins.*/
01175 /*  Map is an array mapping from keys to a list*/
01176 /*  of rows with that key*/
01177 
01178 ret  ::struct::list::Cartesian (type leftmap , type rightmap , type key) {
01179     upvar $leftmap left $rightmap right
01180     set joined [::list]
01181     foreach lrow $left($key) {
01182     foreach row $right($key) {
01183         lappend joined [concat $lrow $row]
01184     }
01185     }
01186     set left($key) $joined
01187     return
01188 }
01189 
01190 ret  ::struct::list::SingleRightCartesian (type mapvar , type key , type rightrow) {
01191     upvar $mapvar map
01192     set joined [::list]
01193     foreach lrow $map($key) {
01194     lappend joined [concat $lrow $rightrow]
01195     }
01196     set map($key) $joined
01197     return
01198 }
01199 
01200 ret  ::struct::list::MapToTable (type mapvar , type keys) {
01201     # Note: keys must not appear multiple times in the list.
01202 
01203     upvar $mapvar map
01204     set table [::list]
01205     foreach k $keys {
01206     foreach row $map($k) {lappend table $row}
01207     }
01208     return $table
01209 }
01210 
01211 /*  More helpers, core join operations: Init, Extend.*/
01212 
01213 ret  ::struct::list::InitMap (type mapvar , type wvar , type key , type table) {
01214     upvar $mapvar map $wvar width
01215     set width [llength [lindex $table 0]]
01216     foreach row $table {
01217     set keyval [lindex $row $key]
01218     if {[info exists map($keyval)]} {
01219         lappend map($keyval) $row
01220     } else {
01221         set map($keyval) [::list $row]
01222     }
01223     }
01224     return [array names map]
01225 }
01226 
01227 ret  ::struct::list::MapExtendInner (type mapvar , type key , type table) {
01228     upvar $mapvar map
01229     array set used {}
01230 
01231     # Phase I - Find all keys in the second table matching keys in the
01232     # first. Remember all their rows.
01233     foreach row $table {
01234     set keyval [lindex $row $key]
01235     if {[info exists map($keyval)]} {
01236         if {[info exists used($keyval)]} {
01237         lappend used($keyval) $row
01238         } else {
01239         set used($keyval) [::list $row]
01240         }
01241     } ; # else: Nothing to do for missing keys.
01242     }
01243 
01244     # Phase II - Merge the collected rows of the second (right) table
01245     # into the map, and eliminate all entries which have no keys in
01246     # the second table.
01247     foreach k [array names map] {
01248     if {[info exists  used($k)]} {
01249         Cartesian map used $k
01250     } else {
01251         unset map($k)
01252     }
01253     }
01254     return [array names map]
01255 }
01256 
01257 ret  ::struct::list::MapExtendRightOuter (type mapvar , type wvar , type key , type table) {
01258     upvar $mapvar map $wvar width
01259     array set used {}
01260 
01261     # Phase I - We keep all keys of the right table, even if they are
01262     # missing in the left one <=> Definition of right outer join.
01263 
01264     set w [llength [lindex $table 0]]
01265     foreach row $table {
01266     set keyval [lindex $row $key]
01267     if {[info exists used($keyval)]} {
01268         lappend used($keyval) $row
01269     } else {
01270         set used($keyval) [::list $row]
01271     }
01272     }
01273 
01274     # Phase II - Merge the collected rows of the second (right) table
01275     # into the map, and eliminate all entries which have no keys in
01276     # the second table. If there is nothing in the left table we
01277     # create an appropriate empty row for the cartesian => definition
01278     # of right outer join.
01279 
01280     # We go through used, because map can be empty for outer
01281 
01282     foreach k [array names map] {
01283     if {![info exists used($k)]} {
01284         unset map($k)
01285     }
01286     }
01287     foreach k [array names used] {
01288     if {![info exists map($k)]} {
01289         set map($k) [::list [Lrepeatn {} $width]]
01290     }
01291     Cartesian map used $k
01292     }
01293 
01294     incr width $w
01295     return [array names map]
01296 }
01297 
01298 ret  ::struct::list::MapExtendLeftOuter (type mapvar , type wvar , type key , type table) {
01299     upvar $mapvar map $wvar width
01300     array set used {}
01301 
01302     ## Keys: All in inner join + additional left keys 
01303     ##       == All left keys = array names map after
01304     ##          all is said and done with it.
01305 
01306     # Phase I - Find all keys in the second table matching keys in the
01307     # first. Remember all their rows.
01308     set w [llength [lindex $table 0]]
01309     foreach row $table {
01310     set keyval [lindex $row $key]
01311     if {[info exists map($keyval)]} {
01312         if {[info exists used($keyval)]} {
01313         lappend used($keyval) $row
01314         } else {
01315         set used($keyval) [::list $row]
01316         }
01317     } ; # else: Nothing to do for missing keys.
01318     }
01319 
01320     # Phase II - Merge the collected rows of the second (right) table
01321     # into the map. We keep entries which have no keys in the second
01322     # table, we actually extend them <=> Left outer join.
01323 
01324     foreach k [array names map] {
01325     if {[info exists  used($k)]} {
01326         Cartesian map used $k
01327     } else {
01328         SingleRightCartesian map $k [Lrepeatn {} $w]
01329     }
01330     }
01331     incr width $w
01332     return [array names map]
01333 }
01334 
01335 ret  ::struct::list::MapExtendFullOuter (type mapvar , type wvar , type key , type table) {
01336     upvar $mapvar map $wvar width
01337     array set used {}
01338 
01339     # Phase I - We keep all keys of the right table, even if they are
01340     # missing in the left one <=> Definition of right outer join.
01341 
01342     set w [llength [lindex $table 0]]
01343     foreach row $table {
01344     set keyval [lindex $row $key]
01345     if {[info exists used($keyval)]} {
01346         lappend used($keyval) $row
01347     } else {
01348         lappend keylist $keyval
01349         set used($keyval) [::list $row]
01350     }
01351     }
01352 
01353     # Phase II - Merge the collected rows of the second (right) table
01354     # into the map. We keep entries which have no keys in the second
01355     # table, we actually extend them <=> Left outer join.
01356     # If there is nothing in the left table we create an appropriate
01357     # empty row for the cartesian => definition of right outer join.
01358 
01359     # We go through used, because map can be empty for outer
01360 
01361     foreach k [array names map] {
01362     if {![info exists used($k)]} {
01363         SingleRightCartesian map $k [Lrepeatn {} $w]
01364     }
01365     }
01366     foreach k [array names used] {
01367     if {![info exists map($k)]} {
01368         set map($k) [::list [Lrepeatn {} $width]]
01369     }
01370     Cartesian map used $k
01371     }
01372 
01373     incr width $w
01374     return [array names map]
01375 }
01376 
01377 /*  Keyed helpers*/
01378 
01379 ret  ::struct::list::InitKeyedMap (type mapvar , type wvar , type table) {
01380     upvar $mapvar map $wvar width
01381     set width [llength [lindex [lindex $table 0] 1]]
01382     foreach row $table {
01383     foreach {keyval rowdata} $row break
01384     if {[info exists map($keyval)]} {
01385         lappend map($keyval) $rowdata
01386     } else {
01387         set map($keyval) [::list $rowdata]
01388     }
01389     }
01390     return [array names map]
01391 }
01392 
01393 ret  ::struct::list::MapKeyedExtendInner (type mapvar , type table) {
01394     upvar $mapvar map
01395     array set used {}
01396 
01397     # Phase I - Find all keys in the second table matching keys in the
01398     # first. Remember all their rows.
01399     foreach row $table {
01400     foreach {keyval rowdata} $row break
01401     if {[info exists map($keyval)]} {
01402         if {[info exists used($keyval)]} {
01403         lappend used($keyval) $rowdata
01404         } else {
01405         set used($keyval) [::list $rowdata]
01406         }
01407     } ; # else: Nothing to do for missing keys.
01408     }
01409 
01410     # Phase II - Merge the collected rows of the second (right) table
01411     # into the map, and eliminate all entries which have no keys in
01412     # the second table.
01413     foreach k [array names map] {
01414     if {[info exists  used($k)]} {
01415         Cartesian map used $k
01416     } else {
01417         unset map($k)
01418     }
01419     }
01420 
01421     return [array names map]
01422 }
01423 
01424 ret  ::struct::list::MapKeyedExtendRightOuter (type mapvar , type wvar , type table) {
01425     upvar $mapvar map $wvar width
01426     array set used {}
01427 
01428     # Phase I - We keep all keys of the right table, even if they are
01429     # missing in the left one <=> Definition of right outer join.
01430 
01431     set w [llength [lindex $table 0]]
01432     foreach row $table {
01433     foreach {keyval rowdata} $row break
01434     if {[info exists used($keyval)]} {
01435         lappend used($keyval) $rowdata
01436     } else {
01437         set used($keyval) [::list $rowdata]
01438     }
01439     }
01440 
01441     # Phase II - Merge the collected rows of the second (right) table
01442     # into the map, and eliminate all entries which have no keys in
01443     # the second table. If there is nothing in the left table we
01444     # create an appropriate empty row for the cartesian => definition
01445     # of right outer join.
01446 
01447     # We go through used, because map can be empty for outer
01448 
01449     foreach k [array names map] {
01450     if {![info exists used($k)]} {
01451         unset map($k)
01452     }
01453     }
01454     foreach k [array names used] {
01455     if {![info exists map($k)]} {
01456         set map($k) [::list [Lrepeatn {} $width]]
01457     }
01458     Cartesian map used $k
01459     }
01460 
01461     incr width $w
01462     return [array names map]
01463 }
01464 
01465 ret  ::struct::list::MapKeyedExtendLeftOuter (type mapvar , type wvar , type table) {
01466     upvar $mapvar map $wvar width
01467     array set used {}
01468 
01469     ## Keys: All in inner join + additional left keys 
01470     ##       == All left keys = array names map after
01471     ##          all is said and done with it.
01472 
01473     # Phase I - Find all keys in the second table matching keys in the
01474     # first. Remember all their rows.
01475     set w [llength [lindex $table 0]]
01476     foreach row $table {
01477     foreach {keyval rowdata} $row break
01478     if {[info exists map($keyval)]} {
01479         if {[info exists used($keyval)]} {
01480         lappend used($keyval) $rowdata
01481         } else {
01482         set used($keyval) [::list $rowdata]
01483         }
01484     } ; # else: Nothing to do for missing keys.
01485     }
01486 
01487     # Phase II - Merge the collected rows of the second (right) table
01488     # into the map. We keep entries which have no keys in the second
01489     # table, we actually extend them <=> Left outer join.
01490 
01491     foreach k [array names map] {
01492     if {[info exists  used($k)]} {
01493         Cartesian map used $k
01494     } else {
01495         SingleRightCartesian map $k [Lrepeatn {} $w]
01496     }
01497     }
01498     incr width $w
01499     return [array names map]
01500 }
01501 
01502 ret  ::struct::list::MapKeyedExtendFullOuter (type mapvar , type wvar , type table) {
01503     upvar $mapvar map $wvar width
01504     array set used {}
01505 
01506     # Phase I - We keep all keys of the right table, even if they are
01507     # missing in the left one <=> Definition of right outer join.
01508 
01509     set w [llength [lindex $table 0]]
01510     foreach row $table {
01511     foreach {keyval rowdata} $row break
01512     if {[info exists used($keyval)]} {
01513         lappend used($keyval) $rowdata
01514     } else {
01515         lappend keylist $keyval
01516         set used($keyval) [::list $rowdata]
01517     }
01518     }
01519 
01520     # Phase II - Merge the collected rows of the second (right) table
01521     # into the map. We keep entries which have no keys in the second
01522     # table, we actually extend them <=> Left outer join.
01523     # If there is nothing in the left table we create an appropriate
01524     # empty row for the cartesian => definition of right outer join.
01525 
01526     # We go through used, because map can be empty for outer
01527 
01528     foreach k [array names map] {
01529     if {![info exists used($k)]} {
01530         SingleRightCartesian map $k [Lrepeatn {} $w]
01531     }
01532     }
01533     foreach k [array names used] {
01534     if {![info exists map($k)]} {
01535         set map($k) [::list [Lrepeatn {} $width]]
01536     }
01537     Cartesian map used $k
01538     }
01539 
01540     incr width $w
01541     return [array names map]
01542 }
01543 
01544 ret  ::struct::list::Dekey (type keyedtable) {
01545     set table [::list]
01546     foreach row $keyedtable {lappend table [lindex $row 1]}
01547     return $table
01548 }
01549 
01550 /*  ::struct::list::Lswap --*/
01551 /* */
01552 /*  Exchange two elements of a list.*/
01553 /* */
01554 /*  Parameters:*/
01555 /*  listvar Name of the variable containing the list to manipulate.*/
01556 /*  i, j    Indices of the list elements to exchange.*/
01557 /* */
01558 /*  Results:*/
01559 /*  The modified list*/
01560 /* */
01561 /*  Side effects:*/
01562 /*        None*/
01563 
01564 ret  ::struct::list::Lswap (type listvar , type i , type j) {
01565     upvar $listvar list
01566 
01567     if {($i < 0) || ($j < 0)} {
01568     return -code error {list index out of range}
01569     }
01570     set len [llength $list]
01571     if {($i >= $len) || ($j >= $len)} {
01572     return -code error {list index out of range}
01573     }
01574 
01575     if {$i != $j} {
01576     set tmp      [lindex $list $i]
01577     lset list $i [lindex $list $j]
01578     lset list $j $tmp
01579     }
01580     return $list
01581 }
01582 
01583 /*  ::struct::list::Lfirstperm --*/
01584 /* */
01585 /*  Returns the lexicographically first permutation of the*/
01586 /*  specified list.*/
01587 /* */
01588 /*  Parameters:*/
01589 /*  list    The list whose first permutation is sought.*/
01590 /* */
01591 /*  Results:*/
01592 /*  A modified list containing the lexicographically first*/
01593 /*  permutation of the input.*/
01594 /* */
01595 /*  Side effects:*/
01596 /*        None*/
01597 
01598 ret  ::struct::list::Lfirstperm (type list) {
01599     return [lsort $list]
01600 }
01601 
01602 /*  ::struct::list::Lnextperm --*/
01603 /* */
01604 /*  Accepts a permutation of a set of elements and returns the*/
01605 /*  next permutatation in lexicographic sequence.*/
01606 /* */
01607 /*  Parameters:*/
01608 /*  list    The list containing the current permutation.*/
01609 /* */
01610 /*  Results:*/
01611 /*  A modified list containing the lexicographically next*/
01612 /*  permutation after the input permutation.*/
01613 /* */
01614 /*  Side effects:*/
01615 /*        None*/
01616 
01617 ret  ::struct::list::Lnextperm (type perm) {
01618     # Find the smallest subscript j such that we have already visited
01619     # all permutations beginning with the first j elements.
01620 
01621     set len [expr {[llength $perm] - 1}]
01622 
01623     set j $len
01624     set ajp1 [lindex $perm $j]
01625     while { $j > 0 } {
01626     incr j -1
01627     set aj [lindex $perm $j]
01628     if { [string compare $ajp1 $aj] > 0 } {
01629         set foundj {}
01630         break
01631     }
01632     set ajp1 $aj
01633     }
01634     if { ![info exists foundj] } return
01635 
01636     # Find the smallest element greater than the j'th among the elements
01637     # following aj. Let its index be l, and interchange aj and al.
01638 
01639     set l $len
01640     while { $aj >= [set al [lindex $perm $l]] } {
01641     incr l -1
01642     }
01643     lset perm $j $al
01644     lset perm $l $aj
01645 
01646     # Reverse a_j+1 ... an
01647 
01648     set k [expr {$j + 1}]
01649     set l $len
01650     while { $k < $l } {
01651     set al [lindex $perm $l]
01652     lset perm $l [lindex $perm $k]
01653     lset perm $k $al
01654     incr k
01655     incr l -1
01656     }
01657 
01658     return $perm
01659 }
01660 
01661 /*  ::struct::list::Lpermutations --*/
01662 /* */
01663 /*  Returns a list containing all the permutations of the*/
01664 /*  specified list, in lexicographic order.*/
01665 /* */
01666 /*  Parameters:*/
01667 /*  list    The list whose permutations are sought.*/
01668 /* */
01669 /*  Results:*/
01670 /*  A list of lists, containing all permutations of the*/
01671 /*  input.*/
01672 /* */
01673 /*  Side effects:*/
01674 /*        None*/
01675 
01676 ret  ::struct::list::Lpermutations (type list) {
01677 
01678     if {[llength $list] < 2} {
01679     return [list $list]
01680     }
01681 
01682     set res {}
01683     set p [Lfirstperm $list]
01684     while {[llength $p]} {
01685     lappend res $p
01686     set p [Lnextperm $p]
01687     }
01688     return $res
01689 }
01690 
01691 /*  ::struct::list::Lforeachperm --*/
01692 /* */
01693 /*  Executes a script for all the permutations of the*/
01694 /*  specified list, in lexicographic order.*/
01695 /* */
01696 /*  Parameters:*/
01697 /*  var Name of the loop variable.*/
01698 /*  list    The list whose permutations are sought.*/
01699 /*  body    The tcl script to run per permutation of*/
01700 /*      the input.*/
01701 /* */
01702 /*  Results:*/
01703 /*  The empty string.*/
01704 /* */
01705 /*  Side effects:*/
01706 /*        None*/
01707 
01708 ret  ::struct::list::Lforeachperm (type var , type list , type body) {
01709     upvar $var loopvar
01710 
01711     if {[llength $list] < 2} {
01712     set loopvar $list
01713     # TODO run body.
01714 
01715     # The first invocation of the body, also the last, as only one
01716     # permutation is possible. That makes handling of the result
01717     # codes easier.
01718 
01719     set code [catch {uplevel 1 $body} result]
01720 
01721     # decide what to do upon the return code:
01722     #
01723     #               0 - the body executed successfully
01724     #               1 - the body raised an error
01725     #               2 - the body invoked [return]
01726     #               3 - the body invoked [break]
01727     #               4 - the body invoked [continue]
01728     # everything else - return and pass on the results
01729     #
01730     switch -exact -- $code {
01731         0 {}
01732         1 {
01733         return -errorinfo [ErrorInfoAsCaller uplevel foreachperm]  \
01734             -errorcode $::errorCode -code error $result
01735         }
01736         3 {}
01737         4 {}
01738         default {
01739         # Includes code 2
01740         return -code $code $result
01741         }
01742     }
01743     return
01744     }
01745 
01746     set p [Lfirstperm $list]
01747     while {[llength $p]} {
01748     set loopvar $p
01749 
01750     set code [catch {uplevel 1 $body} result]
01751 
01752     # decide what to do upon the return code:
01753     #
01754     #               0 - the body executed successfully
01755     #               1 - the body raised an error
01756     #               2 - the body invoked [return]
01757     #               3 - the body invoked [break]
01758     #               4 - the body invoked [continue]
01759     # everything else - return and pass on the results
01760     #
01761     switch -exact -- $code {
01762         0 {}
01763         1 {
01764         return -errorinfo [ErrorInfoAsCaller uplevel foreachperm]  \
01765             -errorcode $::errorCode -code error $result
01766         }
01767         3 {
01768         # FRINK: nocheck
01769         return
01770         }
01771         4 {}
01772         default {
01773         return -code $code $result
01774         }
01775     }
01776     set p [Lnextperm $p]
01777     }
01778     return
01779 }
01780 
01781 ret  ::struct::list::ErrorInfoAsCaller (type find , type replace) {
01782     set info $::errorInfo
01783     set i [string last "\n    (\"$find" $info]
01784     if {$i == -1} {return $info}
01785     set result [string range $info 0 [incr i 6]]    ;# keep "\n    (\""
01786     append result $replace          ;# $find -> $replace
01787     incr i [string length $find]
01788     set j [string first ) $info [incr i]]   ;# keep rest of parenthetical
01789     append result [string range $info $i $j]
01790     return $result
01791 }
01792 
01793 /*  ### ### ### ######### ######### #########*/
01794 /*  Ready*/
01795 
01796 namespace ::struct {
01797     /*  Get 'list::list' into the general structure namespace.*/
01798     namespace import -force list::list
01799     namespace export list
01800 }
01801 package provide struct::list 1.6.1
01802 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1