00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
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
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
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
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
00089
00090
00091
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
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
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
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
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
00542
00543
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
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
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580
00581
00582
00583
00584
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
00609
00610 interp alias {} ::struct::list::Lassign {} lassign
00611 }
00612
00613
00614
00615
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626
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
00636
00637
00638
00639
00640
00641
00642
00643
00644
00645
00646
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
00700
00701
00702
00703
00704
00705
00706
00707
00708
00709
00710
00711
00712
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
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736
00737
00738
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
00753
00754
00755
00756
00757
00758
00759
00760
00761
00762
00763
00764
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
00779
00780
00781
00782
00783
00784
00785
00786
00787
00788
00789
00790
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
00807
00808
00809
00810
00811
00812
00813
00814
00815
00816
00817
00818
00819
00820
00821
00822
00823
00824
00825
00826
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
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876
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
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
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
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922
00923
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
00935
00936
00937
00938
00939
00940
00941
00942
00943
00944
00945
00946
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
00964
00965
00966
00967
00968
00969
00970
00971
00972
00973
00974
00975
00976
00977
00978
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
01012
01013 interp alias {} ::struct::list::Lrepeat {} lrepeat
01014 }
01015
01016
01017
01018
01019
01020
01021
01022
01023
01024
01025
01026
01027
01028
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
01175
01176
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
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
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
01551
01552
01553
01554
01555
01556
01557
01558
01559
01560
01561
01562
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
01584
01585
01586
01587
01588
01589
01590
01591
01592
01593
01594
01595
01596
01597
01598 ret ::struct::list::Lfirstperm (type list) {
01599 return [lsort $list]
01600 }
01601
01602
01603
01604
01605
01606
01607
01608
01609
01610
01611
01612
01613
01614
01615
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
01662
01663
01664
01665
01666
01667
01668
01669
01670
01671
01672
01673
01674
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
01692
01693
01694
01695
01696
01697
01698
01699
01700
01701
01702
01703
01704
01705
01706
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
01795
01796 namespace ::struct {
01797
01798 namespace import -force list::list
01799 namespace export list
01800 }
01801 package provide struct::list 1.6.1
01802