1#---------------------------------------------------------------------- 2# 3# list.tcl -- 4# 5# Definitions for extended processing of Tcl lists. 6# 7# Copyright (c) 2003 by Kevin B. Kenny. All rights reserved. 8# 9# See the file "license.terms" for information on usage and redistribution 10# of this file, and for a DISCLAIMER OF ALL WARRANTIES. 11# 12# RCS: @(#) $Id: list.tcl,v 1.25 2008/07/11 22:34:25 andreas_kupries Exp $ 13# 14#---------------------------------------------------------------------- 15 16package require Tcl 8.0 17package require cmdline 18 19namespace eval ::struct { namespace eval list {} } 20 21namespace eval ::struct::list { 22 namespace export list 23 24 if {0} { 25 # Possibly in the future. 26 namespace export Lassign 27 namespace export LdbJoin 28 namespace export LdbJoinOuter 29 namespace export Ldelete 30 namespace export Lequal 31 namespace export Lfilter 32 namespace export Lfilterfor 33 namespace export Lfirstperm 34 namespace export Lflatten 35 namespace export Lfold 36 namespace export Lforeachperm 37 namespace export Liota 38 namespace export LlcsInvert 39 namespace export LlcsInvert2 40 namespace export LlcsInvertMerge 41 namespace export LlcsInvertMerge2 42 namespace export LlongestCommonSubsequence 43 namespace export LlongestCommonSubsequence2 44 namespace export Lmap 45 namespace export Lmapfor 46 namespace export Lnextperm 47 namespace export Lpermutations 48 namespace export Lrepeat 49 namespace export Lrepeatn 50 namespace export Lreverse 51 namespace export Lshift 52 namespace export Lswap 53 } 54} 55 56########################## 57# Public functions 58 59# ::struct::list::list -- 60# 61# Command that access all list commands. 62# 63# Arguments: 64# cmd Name of the subcommand to dispatch to. 65# args Arguments for the subcommand. 66# 67# Results: 68# Whatever the result of the subcommand is. 69 70proc ::struct::list::list {cmd args} { 71 # Do minimal args checks here 72 if { [llength [info level 0]] == 1 } { 73 return -code error "wrong # args: should be \"$cmd ?arg arg ...?\"" 74 } 75 set sub L$cmd 76 if { [llength [info commands ::struct::list::$sub]] == 0 } { 77 set optlist [info commands ::struct::list::L*] 78 set xlist {} 79 foreach p $optlist { 80 lappend xlist [string range $p 1 end] 81 } 82 return -code error \ 83 "bad option \"$cmd\": must be [linsert [join $xlist ", "] "end-1" "or"]" 84 } 85 return [uplevel 1 [linsert $args 0 ::struct::list::$sub]] 86} 87 88########################## 89# Private functions follow 90# 91# Do a compatibility version of [lset] for pre-8.4 versions of Tcl. 92# This version does not do multi-arg [lset]! 93 94proc ::struct::list::K { x y } { set x } 95 96if { [package vcompare [package provide Tcl] 8.4] < 0 } { 97 proc ::struct::list::lset { var index arg } { 98 upvar 1 $var list 99 set list [::lreplace [K $list [set list {}]] $index $index $arg] 100 } 101} 102 103########################## 104# Implementations of the functionality. 105# 106 107# ::struct::list::LlongestCommonSubsequence -- 108# 109# Computes the longest common subsequence of two lists. 110# 111# Parameters: 112# sequence1, sequence2 -- Two lists to compare. 113# maxOccurs -- If provided, causes the procedure to ignore 114# lines that appear more than $maxOccurs times 115# in the second sequence. See below for a discussion. 116# Results: 117# Returns a list of two lists of equal length. 118# The first sublist is of indices into sequence1, and the 119# second sublist is of indices into sequence2. Each corresponding 120# pair of indices corresponds to equal elements in the sequences; 121# the sequence returned is the longest possible. 122# 123# Side effects: 124# None. 125# 126# Notes: 127# 128# While this procedure is quite rapid for many tasks of file 129# comparison, its performance degrades severely if the second list 130# contains many equal elements (as, for instance, when using this 131# procedure to compare two files, a quarter of whose lines are blank. 132# This drawback is intrinsic to the algorithm used (see the References 133# for details). One approach to dealing with this problem that is 134# sometimes effective in practice is arbitrarily to exclude elements 135# that appear more than a certain number of times. This number is 136# provided as the 'maxOccurs' parameter. If frequent lines are 137# excluded in this manner, they will not appear in the common subsequence 138# that is computed; the result will be the longest common subsequence 139# of infrequent elements. 140# 141# The procedure struct::list::LongestCommonSubsequence2 142# functions as a wrapper around this procedure; it computes the longest 143# common subsequence of infrequent elements, and then subdivides the 144# subsequences that lie between the matches to approximate the true 145# longest common subsequence. 146# 147# References: 148# J. W. Hunt and M. D. McIlroy, "An algorithm for differential 149# file comparison," Comp. Sci. Tech. Rep. #41, Bell Telephone 150# Laboratories (1976). Available on the Web at the second 151# author's personal site: http://www.cs.dartmouth.edu/~doug/ 152 153proc ::struct::list::LlongestCommonSubsequence { 154 sequence1 155 sequence2 156 {maxOccurs 0x7fffffff} 157} { 158 # Construct a set of equivalence classes of lines in file 2 159 160 set index 0 161 foreach string $sequence2 { 162 lappend eqv($string) $index 163 incr index 164 } 165 166 # K holds descriptions of the common subsequences. 167 # Initially, there is one common subsequence of length 0, 168 # with a fence saying that it includes line -1 of both files. 169 # The maximum subsequence length is 0; position 0 of 170 # K holds a fence carrying the line following the end 171 # of both files. 172 173 lappend K [::list -1 -1 {}] 174 lappend K [::list [llength $sequence1] [llength $sequence2] {}] 175 set k 0 176 177 # Walk through the first file, letting i be the index of the line and 178 # string be the line itself. 179 180 set i 0 181 foreach string $sequence1 { 182 # Consider each possible corresponding index j in the second file. 183 184 if { [info exists eqv($string)] 185 && [llength $eqv($string)] <= $maxOccurs } { 186 187 # c is the candidate match most recently found, and r is the 188 # length of the corresponding subsequence. 189 190 set r 0 191 set c [lindex $K 0] 192 193 foreach j $eqv($string) { 194 # Perform a binary search to find a candidate common 195 # subsequence to which may be appended this match. 196 197 set max $k 198 set min $r 199 set s [expr { $k + 1 }] 200 while { $max >= $min } { 201 set mid [expr { ( $max + $min ) / 2 }] 202 set bmid [lindex [lindex $K $mid] 1] 203 if { $j == $bmid } { 204 break 205 } elseif { $j < $bmid } { 206 set max [expr {$mid - 1}] 207 } else { 208 set s $mid 209 set min [expr { $mid + 1 }] 210 } 211 } 212 213 # Go to the next match point if there is no suitable 214 # candidate. 215 216 if { $j == [lindex [lindex $K $mid] 1] || $s > $k} { 217 continue 218 } 219 220 # s is the sequence length of the longest sequence 221 # to which this match point may be appended. Make 222 # a new candidate match and store the old one in K 223 # Set r to the length of the new candidate match. 224 225 set newc [::list $i $j [lindex $K $s]] 226 if { $r >= 0 } { 227 lset K $r $c 228 } 229 set c $newc 230 set r [expr { $s + 1 }] 231 232 # If we've extended the length of the longest match, 233 # we're done; move the fence. 234 235 if { $s >= $k } { 236 lappend K [lindex $K end] 237 incr k 238 break 239 } 240 } 241 242 # Put the last candidate into the array 243 244 lset K $r $c 245 } 246 247 incr i 248 } 249 250 # Package the common subsequence in a convenient form 251 252 set seta {} 253 set setb {} 254 set q [lindex $K $k] 255 256 for { set i 0 } { $i < $k } {incr i } { 257 lappend seta {} 258 lappend setb {} 259 } 260 while { [lindex $q 0] >= 0 } { 261 incr k -1 262 lset seta $k [lindex $q 0] 263 lset setb $k [lindex $q 1] 264 set q [lindex $q 2] 265 } 266 267 return [::list $seta $setb] 268} 269 270# ::struct::list::LlongestCommonSubsequence2 -- 271# 272# Derives an approximation to the longest common subsequence 273# of two lists. 274# 275# Parameters: 276# sequence1, sequence2 - Lists to be compared 277# maxOccurs - Parameter for imprecise matching - see below. 278# 279# Results: 280# Returns a list of two lists of equal length. 281# The first sublist is of indices into sequence1, and the 282# second sublist is of indices into sequence2. Each corresponding 283# pair of indices corresponds to equal elements in the sequences; 284# the sequence returned is an approximation to the longest possible. 285# 286# Side effects: 287# None. 288# 289# Notes: 290# This procedure acts as a wrapper around the companion procedure 291# struct::list::LongestCommonSubsequence and accepts the same 292# parameters. It first computes the longest common subsequence of 293# elements that occur no more than $maxOccurs times in the 294# second list. Using that subsequence to align the two lists, 295# it then tries to augment the subsequence by computing the true 296# longest common subsequences of the sublists between matched pairs. 297 298proc ::struct::list::LlongestCommonSubsequence2 { 299 sequence1 300 sequence2 301 {maxOccurs 0x7fffffff} 302} { 303 # Derive the longest common subsequence of elements that occur at 304 # most $maxOccurs times 305 306 foreach { l1 l2 } \ 307 [LlongestCommonSubsequence $sequence1 $sequence2 $maxOccurs] { 308 break 309 } 310 311 # Walk through the match points in the sequence just derived. 312 313 set result1 {} 314 set result2 {} 315 set n1 0 316 set n2 0 317 foreach i1 $l1 i2 $l2 { 318 if { $i1 != $n1 && $i2 != $n2 } { 319 # The match points indicate that there are unmatched 320 # elements lying between them in both input sequences. 321 # Extract the unmatched elements and perform precise 322 # longest-common-subsequence analysis on them. 323 324 set subl1 [lrange $sequence1 $n1 [expr { $i1 - 1 }]] 325 set subl2 [lrange $sequence2 $n2 [expr { $i2 - 1 }]] 326 foreach { m1 m2 } [LlongestCommonSubsequence $subl1 $subl2] break 327 foreach j1 $m1 j2 $m2 { 328 lappend result1 [expr { $j1 + $n1 }] 329 lappend result2 [expr { $j2 + $n2 }] 330 } 331 } 332 333 # Add the current match point to the result 334 335 lappend result1 $i1 336 lappend result2 $i2 337 set n1 [expr { $i1 + 1 }] 338 set n2 [expr { $i2 + 1 }] 339 } 340 341 # If there are unmatched elements after the last match in both files, 342 # perform precise longest-common-subsequence matching on them and 343 # add the result to our return. 344 345 if { $n1 < [llength $sequence1] && $n2 < [llength $sequence2] } { 346 set subl1 [lrange $sequence1 $n1 end] 347 set subl2 [lrange $sequence2 $n2 end] 348 foreach { m1 m2 } [LlongestCommonSubsequence $subl1 $subl2] break 349 foreach j1 $m1 j2 $m2 { 350 lappend result1 [expr { $j1 + $n1 }] 351 lappend result2 [expr { $j2 + $n2 }] 352 } 353 } 354 355 return [::list $result1 $result2] 356} 357 358# ::struct::list::LlcsInvert -- 359# 360# Takes the data describing a longest common subsequence of two 361# lists and inverts the information in the sense that the result 362# of this command will describe the differences between the two 363# sequences instead of the identical parts. 364# 365# Parameters: 366# lcsData longest common subsequence of two lists as 367# returned by longestCommonSubsequence(2). 368# Results: 369# Returns a single list whose elements describe the differences 370# between the original two sequences. Each element describes 371# one difference through three pieces, the type of the change, 372# a pair of indices in the first sequence and a pair of indices 373# into the second sequence, in this order. 374# 375# Side effects: 376# None. 377 378proc ::struct::list::LlcsInvert {lcsData len1 len2} { 379 return [LlcsInvert2 [::lindex $lcsData 0] [::lindex $lcsData 1] $len1 $len2] 380} 381 382proc ::struct::list::LlcsInvert2 {idx1 idx2 len1 len2} { 383 set result {} 384 set last1 -1 385 set last2 -1 386 387 foreach a $idx1 b $idx2 { 388 # Four possible cases. 389 # a) last1 ... a and last2 ... b are not empty. 390 # This is a 'change'. 391 # b) last1 ... a is empty, last2 ... b is not. 392 # This is an 'addition'. 393 # c) last1 ... a is not empty, last2 ... b is empty. 394 # This is a deletion. 395 # d) If both ranges are empty we can ignore the 396 # two current indices. 397 398 set empty1 [expr {($a - $last1) <= 1}] 399 set empty2 [expr {($b - $last2) <= 1}] 400 401 if {$empty1 && $empty2} { 402 # Case (d), ignore the indices 403 } elseif {$empty1} { 404 # Case (b), 'addition'. 405 incr last2 ; incr b -1 406 lappend result [::list added [::list $last1 $a] [::list $last2 $b]] 407 incr b 408 } elseif {$empty2} { 409 # Case (c), 'deletion' 410 incr last1 ; incr a -1 411 lappend result [::list deleted [::list $last1 $a] [::list $last2 $b]] 412 incr a 413 } else { 414 # Case (q), 'change'. 415 incr last1 ; incr a -1 416 incr last2 ; incr b -1 417 lappend result [::list changed [::list $last1 $a] [::list $last2 $b]] 418 incr a 419 incr b 420 } 421 422 set last1 $a 423 set last2 $b 424 } 425 426 # Handle the last chunk, using the information about the length of 427 # the original sequences. 428 429 set empty1 [expr {($len1 - $last1) <= 1}] 430 set empty2 [expr {($len2 - $last2) <= 1}] 431 432 if {$empty1 && $empty2} { 433 # Case (d), ignore the indices 434 } elseif {$empty1} { 435 # Case (b), 'addition'. 436 incr last2 ; incr len2 -1 437 lappend result [::list added [::list $last1 $len1] [::list $last2 $len2]] 438 } elseif {$empty2} { 439 # Case (c), 'deletion' 440 incr last1 ; incr len1 -1 441 lappend result [::list deleted [::list $last1 $len1] [::list $last2 $len2]] 442 } else { 443 # Case (q), 'change'. 444 incr last1 ; incr len1 -1 445 incr last2 ; incr len2 -1 446 lappend result [::list changed [::list $last1 $len1] [::list $last2 $len2]] 447 } 448 449 return $result 450} 451 452proc ::struct::list::LlcsInvertMerge {lcsData len1 len2} { 453 return [LlcsInvertMerge2 [::lindex $lcsData 0] [::lindex $lcsData 1] $len1 $len2] 454} 455 456proc ::struct::list::LlcsInvertMerge2 {idx1 idx2 len1 len2} { 457 set result {} 458 set last1 -1 459 set last2 -1 460 461 foreach a $idx1 b $idx2 { 462 # Four possible cases. 463 # a) last1 ... a and last2 ... b are not empty. 464 # This is a 'change'. 465 # b) last1 ... a is empty, last2 ... b is not. 466 # This is an 'addition'. 467 # c) last1 ... a is not empty, last2 ... b is empty. 468 # This is a deletion. 469 # d) If both ranges are empty we can ignore the 470 # two current indices. For merging we simply 471 # take the information from the input. 472 473 set empty1 [expr {($a - $last1) <= 1}] 474 set empty2 [expr {($b - $last2) <= 1}] 475 476 if {$empty1 && $empty2} { 477 # Case (d), add 'unchanged' chunk. 478 set type -- 479 foreach {type left right} [lindex $result end] break 480 if {[string match unchanged $type]} { 481 # There is an existing result to extend 482 lset left end $a 483 lset right end $b 484 lset result end [::list unchanged $left $right] 485 } else { 486 # There is an unchanged result at the start of the list; 487 # it may be extended. 488 lappend result [::list unchanged [::list $a $a] [::list $b $b]] 489 } 490 } else { 491 if {$empty1} { 492 # Case (b), 'addition'. 493 incr last2 ; incr b -1 494 lappend result [::list added [::list $last1 $a] [::list $last2 $b]] 495 incr b 496 } elseif {$empty2} { 497 # Case (c), 'deletion' 498 incr last1 ; incr a -1 499 lappend result [::list deleted [::list $last1 $a] [::list $last2 $b]] 500 incr a 501 } else { 502 # Case (a), 'change'. 503 incr last1 ; incr a -1 504 incr last2 ; incr b -1 505 lappend result [::list changed [::list $last1 $a] [::list $last2 $b]] 506 incr a 507 incr b 508 } 509 # Finally, the two matching lines are a new unchanged region 510 lappend result [::list unchanged [::list $a $a] [::list $b $b]] 511 } 512 set last1 $a 513 set last2 $b 514 } 515 516 # Handle the last chunk, using the information about the length of 517 # the original sequences. 518 519 set empty1 [expr {($len1 - $last1) <= 1}] 520 set empty2 [expr {($len2 - $last2) <= 1}] 521 522 if {$empty1 && $empty2} { 523 # Case (d), ignore the indices 524 } elseif {$empty1} { 525 # Case (b), 'addition'. 526 incr last2 ; incr len2 -1 527 lappend result [::list added [::list $last1 $len1] [::list $last2 $len2]] 528 } elseif {$empty2} { 529 # Case (c), 'deletion' 530 incr last1 ; incr len1 -1 531 lappend result [::list deleted [::list $last1 $len1] [::list $last2 $len2]] 532 } else { 533 # Case (q), 'change'. 534 incr last1 ; incr len1 -1 535 incr last2 ; incr len2 -1 536 lappend result [::list changed [::list $last1 $len1] [::list $last2 $len2]] 537 } 538 539 return $result 540} 541 542# ::struct::list::Lreverse -- 543# 544# Reverses the contents of the list and returns the reversed 545# list as the result of the command. 546# 547# Parameters: 548# sequence List to be reversed. 549# 550# Results: 551# The sequence in reverse. 552# 553# Side effects: 554# None. 555 556proc ::struct::list::Lreverse {sequence} { 557 set l [::llength $sequence] 558 559 # Shortcut for lists where reversing yields the list itself 560 if {$l < 2} {return $sequence} 561 562 # Perform true reversal 563 set res [::list] 564 while {$l} { 565 ::lappend res [::lindex $sequence [incr l -1]] 566 } 567 return $res 568} 569 570 571# ::struct::list::Lassign -- 572# 573# Assign list elements to variables. 574# 575# Parameters: 576# sequence List to assign 577# args Names of the variables to assign to. 578# 579# Results: 580# The unassigned part of the sequence. Can be empty. 581# 582# Side effects: 583# None. 584 585# Do a compatibility version of [assign] for pre-8.5 versions of Tcl. 586 587if { [package vcompare [package provide Tcl] 8.5] < 0 } { 588 # 8.4 589 proc ::struct::list::Lassign {sequence v args} { 590 set args [linsert $args 0 $v] 591 set a [::llength $args] 592 593 # Nothing to assign. 594 #if {$a == 0} {return $sequence} 595 596 # Perform assignments 597 set i 0 598 foreach v $args { 599 upvar 1 $v var 600 set var [::lindex $sequence $i] 601 incr i 602 } 603 604 # Return remainder, if there is any. 605 return [::lrange $sequence $a end] 606} 607 608} else { 609 # For 8.5+ simply redirect the method to the core command. 610 611 interp alias {} ::struct::list::Lassign {} lassign 612} 613 614 615# ::struct::list::Lshift -- 616# 617# Shift a list in a variable one element down, and return first element 618# 619# Parameters: 620# listvar Name of variable containing the list to shift. 621# 622# Results: 623# The first element of the list. 624# 625# Side effects: 626# After the call the list variable will contain 627# the second to last elements of the list. 628 629proc ::struct::list::Lshift {listvar} { 630 upvar 1 $listvar list 631 set list [Lassign [K $list [set list {}]] v] 632 return $v 633} 634 635 636# ::struct::list::Lflatten -- 637# 638# Remove nesting from the input 639# 640# Parameters: 641# sequence List to flatten 642# 643# Results: 644# The input list with one or all levels of nesting removed. 645# 646# Side effects: 647# None. 648 649proc ::struct::list::Lflatten {args} { 650 if {[::llength $args] < 1} { 651 return -code error \ 652 "wrong#args: should be \"::struct::list::Lflatten ?-full? ?--? sequence\"" 653 } 654 655 set full 0 656 while {[string match -* [set opt [::lindex $args 0]]]} { 657 switch -glob -- $opt { 658 -full {set full 1} 659 -- {break} 660 default { 661 return -code error "Unknown option \"$opt\", should be either -full, or --" 662 } 663 } 664 set args [::lrange $args 1 end] 665 } 666 667 if {[::llength $args] != 1} { 668 return -code error \ 669 "wrong#args: should be \"::struct::list::Lflatten ?-full? ?--? sequence\"" 670 } 671 672 set sequence [::lindex $args 0] 673 set cont 1 674 while {$cont} { 675 set cont 0 676 set result [::list] 677 foreach item $sequence { 678 # catch/llength detects if the item is following the list 679 # syntax. 680 681 if {[catch {llength $item} len]} { 682 # Element is not a list in itself, no flatten, add it 683 # as is. 684 lappend result $item 685 } else { 686 # Element is parseable as list, add all sub-elements 687 # to the result. 688 foreach e $item { 689 lappend result $e 690 } 691 } 692 } 693 if {$full && [string compare $sequence $result]} {set cont 1} 694 set sequence $result 695 } 696 return $result 697} 698 699 700# ::struct::list::Lmap -- 701# 702# Apply command to each element of a list and return concatenated results. 703# 704# Parameters: 705# sequence List to operate on 706# cmdprefix Operation to perform on the elements. 707# 708# Results: 709# List containing the result of applying cmdprefix to the elements of the 710# sequence. 711# 712# Side effects: 713# None of its own, but the command prefix can perform arbitry actions. 714 715proc ::struct::list::Lmap {sequence cmdprefix} { 716 # Shortcut when nothing is to be done. 717 if {[::llength $sequence] == 0} {return $sequence} 718 719 set res [::list] 720 foreach item $sequence { 721 lappend res [uplevel 1 [linsert $cmdprefix end $item]] 722 } 723 return $res 724} 725 726# ::struct::list::Lmapfor -- 727# 728# Apply a script to each element of a list and return concatenated results. 729# 730# Parameters: 731# sequence List to operate on 732# script The script to run on the elements. 733# 734# Results: 735# List containing the result of running script on the elements of the 736# sequence. 737# 738# Side effects: 739# None of its own, but the script can perform arbitry actions. 740 741proc ::struct::list::Lmapfor {var sequence script} { 742 # Shortcut when nothing is to be done. 743 if {[::llength $sequence] == 0} {return $sequence} 744 upvar 1 $var item 745 746 set res [::list] 747 foreach item $sequence { 748 lappend res [uplevel 1 $script] 749 } 750 return $res 751} 752 753# ::struct::list::Lfilter -- 754# 755# Apply command to each element of a list and return elements passing the test. 756# 757# Parameters: 758# sequence List to operate on 759# cmdprefix Test to perform on the elements. 760# 761# Results: 762# List containing the elements of the input passing the test command. 763# 764# Side effects: 765# None of its own, but the command prefix can perform arbitrary actions. 766 767proc ::struct::list::Lfilter {sequence cmdprefix} { 768 # Shortcut when nothing is to be done. 769 if {[::llength $sequence] == 0} {return $sequence} 770 return [Lfold $sequence {} [::list ::struct::list::FTest $cmdprefix]] 771} 772 773proc ::struct::list::FTest {cmdprefix result item} { 774 set pass [uplevel 1 [::linsert $cmdprefix end $item]] 775 if {$pass} {::lappend result $item} 776 return $result 777} 778 779# ::struct::list::Lfilterfor -- 780# 781# Apply expr condition to each element of a list and return elements passing the test. 782# 783# Parameters: 784# sequence List to operate on 785# expr Test to perform on the elements. 786# 787# Results: 788# List containing the elements of the input passing the test expression. 789# 790# Side effects: 791# None of its own, but the command prefix can perform arbitrary actions. 792 793proc ::struct::list::Lfilterfor {var sequence expr} { 794 # Shortcut when nothing is to be done. 795 if {[::llength $sequence] == 0} {return $sequence} 796 797 upvar 1 $var item 798 set result {} 799 foreach item $sequence { 800 if {[uplevel 1 [::list ::expr $expr]]} { 801 lappend result $item 802 } 803 } 804 return $result 805} 806 807# ::struct::list::Lsplit -- 808# 809# Apply command to each element of a list and return elements passing 810# and failing the test. Basic idea by Salvatore Sanfilippo 811# (http://wiki.tcl.tk/lsplit). The implementation here is mine (AK), 812# and the interface is slightly different (Command prefix with the 813# list element given to it as argument vs. variable + script). 814# 815# Parameters: 816# sequence List to operate on 817# cmdprefix Test to perform on the elements. 818# args = empty | (varPass varFail) 819# 820# Results: 821# If the variables are specified then a list containing the 822# numbers of passing and failing elements, in this 823# order. Otherwise a list having two elements, the lists of 824# passing and failing elements, in this order. 825# 826# Side effects: 827# None of its own, but the command prefix can perform arbitrary actions. 828 829proc ::struct::list::Lsplit {sequence cmdprefix args} { 830 set largs [::llength $args] 831 if {$largs == 0} { 832 # Shortcut when nothing is to be done. 833 if {[::llength $sequence] == 0} {return {{} {}}} 834 return [Lfold $sequence {} [::list ::struct::list::PFTest $cmdprefix]] 835 } elseif {$largs == 2} { 836 # Shortcut when nothing is to be done. 837 foreach {pv fv} $args break 838 upvar 1 $pv pass $fv fail 839 if {[::llength $sequence] == 0} { 840 set pass {} 841 set fail {} 842 return {0 0} 843 } 844 foreach {pass fail} [Lfold $sequence {} [::list ::struct::list::PFTest $cmdprefix]] break 845 return [::list [llength $pass] [llength $fail]] 846 } else { 847 return -code error \ 848 "wrong#args: should be \"::struct::list::Lsplit sequence cmdprefix ?passVar failVar?" 849 } 850} 851 852proc ::struct::list::PFTest {cmdprefix result item} { 853 set passing [uplevel 1 [::linsert $cmdprefix end $item]] 854 set pass {} ; set fail {} 855 foreach {pass fail} $result break 856 if {$passing} { 857 ::lappend pass $item 858 } else { 859 ::lappend fail $item 860 } 861 return [::list $pass $fail] 862} 863 864# ::struct::list::Lfold -- 865# 866# Fold list into one value. 867# 868# Parameters: 869# sequence List to operate on 870# cmdprefix Operation to perform on the elements. 871# 872# Results: 873# Result of applying cmdprefix to the elements of the 874# sequence. 875# 876# Side effects: 877# None of its own, but the command prefix can perform arbitry actions. 878 879proc ::struct::list::Lfold {sequence initialvalue cmdprefix} { 880 # Shortcut when nothing is to be done. 881 if {[::llength $sequence] == 0} {return $initialvalue} 882 883 set res $initialvalue 884 foreach item $sequence { 885 set res [uplevel 1 [linsert $cmdprefix end $res $item]] 886 } 887 return $res 888} 889 890# ::struct::list::Liota -- 891# 892# Return a list containing the integer numbers 0 ... n-1 893# 894# Parameters: 895# n First number not in the generated list. 896# 897# Results: 898# A list containing integer numbers. 899# 900# Side effects: 901# None 902 903proc ::struct::list::Liota {n} { 904 set retval [::list] 905 for {set i 0} {$i < $n} {incr i} { 906 ::lappend retval $i 907 } 908 return $retval 909} 910 911# ::struct::list::Ldelete -- 912# 913# Delete an element from a list by name. 914# Similar to 'struct::set exclude', however 915# this here preserves order and list intrep. 916# 917# Parameters: 918# a First list to compare. 919# b Second list to compare. 920# 921# Results: 922# A boolean. True if the lists are delete. 923# 924# Side effects: 925# None 926 927proc ::struct::list::Ldelete {var item} { 928 upvar 1 $var list 929 set pos [lsearch -exact $list $item] 930 if {$pos < 0} return 931 set list [lreplace [K $list [set list {}]] $pos $pos] 932 return 933} 934 935# ::struct::list::Lequal -- 936# 937# Compares two lists for equality 938# (Same length, Same elements in same order). 939# 940# Parameters: 941# a First list to compare. 942# b Second list to compare. 943# 944# Results: 945# A boolean. True if the lists are equal. 946# 947# Side effects: 948# None 949 950proc ::struct::list::Lequal {a b} { 951 # Author of this command is "Richard Suchenwirth" 952 953 if {[::llength $a] != [::llength $b]} {return 0} 954 if {[::lindex $a 0] == $a} {return [string equal $a $b]} 955 foreach i $a j $b {if {![Lequal $i $j]} {return 0}} 956 return 1 957} 958 959# ::struct::list::Lrepeatn -- 960# 961# Create a list repeating the same value over again. 962# 963# Parameters: 964# value value to use in the created list. 965# args Dimension(s) of the (nested) list to create. 966# 967# Results: 968# A list 969# 970# Side effects: 971# None 972 973proc ::struct::list::Lrepeatn {value args} { 974 if {[::llength $args] == 1} {set args [::lindex $args 0]} 975 set buf {} 976 foreach number $args { 977 incr number 0 ;# force integer (1) 978 set buf {} 979 for {set i 0} {$i<$number} {incr i} { 980 ::lappend buf $value 981 } 982 set value $buf 983 } 984 return $buf 985 # (1): See 'Stress testing' (wiki) for why this makes the code safer. 986} 987 988# ::struct::list::Lrepeat -- 989# 990# Create a list repeating the same value over again. 991# [Identical to the Tcl 8.5 lrepeat command] 992# 993# Parameters: 994# n Number of replications. 995# args values to use in the created list. 996# 997# Results: 998# A list 999# 1000# Side effects: 1001# None 1002 1003# Do a compatibility version of [repeat] for pre-8.5 versions of Tcl. 1004 1005if { [package vcompare [package provide Tcl] 8.5] < 0 } { 1006 1007 proc ::struct::list::Lrepeat {positiveCount value args} { 1008 if {![string is integer -strict $positiveCount]} { 1009 return -code error "expected integer but got \"$positiveCount\"" 1010 } elseif {$positiveCount < 1} { 1011 return -code error {must have a count of at least 1} 1012 } 1013 1014 set args [linsert $args 0 $value] 1015 1016 if {$positiveCount == 1} { 1017 # Tcl itself has already listified the incoming parameters 1018 # via 'args'. 1019 return $args 1020 } 1021 1022 set result [::list] 1023 while {$positiveCount > 0} { 1024 if {($positiveCount % 2) == 0} { 1025 set args [concat $args $args] 1026 set positiveCount [expr {$positiveCount/2}] 1027 } else { 1028 set result [concat $result $args] 1029 incr positiveCount -1 1030 } 1031 } 1032 return $result 1033 } 1034 1035} else { 1036 # For 8.5 simply redirect the method to the core command. 1037 1038 interp alias {} ::struct::list::Lrepeat {} lrepeat 1039} 1040 1041# ::struct::list::LdbJoin(Keyed) -- 1042# 1043# Relational table joins. 1044# 1045# Parameters: 1046# args key specs and tables to join 1047# 1048# Results: 1049# A table/matrix as nested list. See 1050# struct/matrix set/get rect for structure. 1051# 1052# Side effects: 1053# None 1054 1055proc ::struct::list::LdbJoin {args} { 1056 # -------------------------------- 1057 # Process options ... 1058 1059 set mode inner 1060 set keyvar {} 1061 1062 while {[llength $args]} { 1063 set err [::cmdline::getopt args {inner left right full keys.arg} opt arg] 1064 if {$err == 1} { 1065 if {[string equal $opt keys]} { 1066 set keyvar $arg 1067 } else { 1068 set mode $opt 1069 } 1070 } elseif {$err < 0} { 1071 return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? ?-keys varname? \{key table\}..." 1072 } else { 1073 # Non-option argument found, stop processing. 1074 break 1075 } 1076 } 1077 1078 set inner [string equal $mode inner] 1079 set innerorleft [expr {$inner || [string equal $mode left]}] 1080 1081 # -------------------------------- 1082 # Process tables ... 1083 1084 if {([llength $args] % 2) != 0} { 1085 return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? \{key table\}..." 1086 } 1087 1088 # One table only, join is identity 1089 if {[llength $args] == 2} {return [lindex $args 1]} 1090 1091 # Use first table for setup. 1092 1093 foreach {key table} $args break 1094 1095 # Check for possible early abort 1096 if {$innerorleft && ([llength $table] == 0)} {return {}} 1097 1098 set width 0 1099 array set state {} 1100 1101 set keylist [InitMap state width $key $table] 1102 1103 # Extend state with the remaining tables. 1104 1105 foreach {key table} [lrange $args 2 end] { 1106 # Check for possible early abort 1107 if {$inner && ([llength $table] == 0)} {return {}} 1108 1109 switch -exact -- $mode { 1110 inner {set keylist [MapExtendInner state $key $table]} 1111 left {set keylist [MapExtendLeftOuter state width $key $table]} 1112 right {set keylist [MapExtendRightOuter state width $key $table]} 1113 full {set keylist [MapExtendFullOuter state width $key $table]} 1114 } 1115 1116 # Check for possible early abort 1117 if {$inner && ([llength $keylist] == 0)} {return {}} 1118 } 1119 1120 if {[string length $keyvar]} { 1121 upvar 1 $keyvar keys 1122 set keys $keylist 1123 } 1124 1125 return [MapToTable state $keylist] 1126} 1127 1128proc ::struct::list::LdbJoinKeyed {args} { 1129 # -------------------------------- 1130 # Process options ... 1131 1132 set mode inner 1133 set keyvar {} 1134 1135 while {[llength $args]} { 1136 set err [::cmdline::getopt args {inner left right full keys.arg} opt arg] 1137 if {$err == 1} { 1138 if {[string equal $opt keys]} { 1139 set keyvar $arg 1140 } else { 1141 set mode $opt 1142 } 1143 } elseif {$err < 0} { 1144 return -code error "wrong#args: dbJoin ?-inner|-left|-right|-full? table..." 1145 } else { 1146 # Non-option argument found, stop processing. 1147 break 1148 } 1149 } 1150 1151 set inner [string equal $mode inner] 1152 set innerorleft [expr {$inner || [string equal $mode left]}] 1153 1154 # -------------------------------- 1155 # Process tables ... 1156 1157 # One table only, join is identity 1158 if {[llength $args] == 1} { 1159 return [Dekey [lindex $args 0]] 1160 } 1161 1162 # Use first table for setup. 1163 1164 set table [lindex $args 0] 1165 1166 # Check for possible early abort 1167 if {$innerorleft && ([llength $table] == 0)} {return {}} 1168 1169 set width 0 1170 array set state {} 1171 1172 set keylist [InitKeyedMap state width $table] 1173 1174 # Extend state with the remaining tables. 1175 1176 foreach table [lrange $args 1 end] { 1177 # Check for possible early abort 1178 if {$inner && ([llength $table] == 0)} {return {}} 1179 1180 switch -exact -- $mode { 1181 inner {set keylist [MapKeyedExtendInner state $table]} 1182 left {set keylist [MapKeyedExtendLeftOuter state width $table]} 1183 right {set keylist [MapKeyedExtendRightOuter state width $table]} 1184 full {set keylist [MapKeyedExtendFullOuter state width $table]} 1185 } 1186 1187 # Check for possible early abort 1188 if {$inner && ([llength $keylist] == 0)} {return {}} 1189 } 1190 1191 if {[string length $keyvar]} { 1192 upvar 1 $keyvar keys 1193 set keys $keylist 1194 } 1195 1196 return [MapToTable state $keylist] 1197} 1198 1199## Helpers for the relational joins. 1200## Map is an array mapping from keys to a list 1201## of rows with that key 1202 1203proc ::struct::list::Cartesian {leftmap rightmap key} { 1204 upvar $leftmap left $rightmap right 1205 set joined [::list] 1206 foreach lrow $left($key) { 1207 foreach row $right($key) { 1208 lappend joined [concat $lrow $row] 1209 } 1210 } 1211 set left($key) $joined 1212 return 1213} 1214 1215proc ::struct::list::SingleRightCartesian {mapvar key rightrow} { 1216 upvar $mapvar map 1217 set joined [::list] 1218 foreach lrow $map($key) { 1219 lappend joined [concat $lrow $rightrow] 1220 } 1221 set map($key) $joined 1222 return 1223} 1224 1225proc ::struct::list::MapToTable {mapvar keys} { 1226 # Note: keys must not appear multiple times in the list. 1227 1228 upvar $mapvar map 1229 set table [::list] 1230 foreach k $keys { 1231 foreach row $map($k) {lappend table $row} 1232 } 1233 return $table 1234} 1235 1236## More helpers, core join operations: Init, Extend. 1237 1238proc ::struct::list::InitMap {mapvar wvar key table} { 1239 upvar $mapvar map $wvar width 1240 set width [llength [lindex $table 0]] 1241 foreach row $table { 1242 set keyval [lindex $row $key] 1243 if {[info exists map($keyval)]} { 1244 lappend map($keyval) $row 1245 } else { 1246 set map($keyval) [::list $row] 1247 } 1248 } 1249 return [array names map] 1250} 1251 1252proc ::struct::list::MapExtendInner {mapvar key table} { 1253 upvar $mapvar map 1254 array set used {} 1255 1256 # Phase I - Find all keys in the second table matching keys in the 1257 # first. Remember all their rows. 1258 foreach row $table { 1259 set keyval [lindex $row $key] 1260 if {[info exists map($keyval)]} { 1261 if {[info exists used($keyval)]} { 1262 lappend used($keyval) $row 1263 } else { 1264 set used($keyval) [::list $row] 1265 } 1266 } ; # else: Nothing to do for missing keys. 1267 } 1268 1269 # Phase II - Merge the collected rows of the second (right) table 1270 # into the map, and eliminate all entries which have no keys in 1271 # the second table. 1272 foreach k [array names map] { 1273 if {[info exists used($k)]} { 1274 Cartesian map used $k 1275 } else { 1276 unset map($k) 1277 } 1278 } 1279 return [array names map] 1280} 1281 1282proc ::struct::list::MapExtendRightOuter {mapvar wvar key table} { 1283 upvar $mapvar map $wvar width 1284 array set used {} 1285 1286 # Phase I - We keep all keys of the right table, even if they are 1287 # missing in the left one <=> Definition of right outer join. 1288 1289 set w [llength [lindex $table 0]] 1290 foreach row $table { 1291 set keyval [lindex $row $key] 1292 if {[info exists used($keyval)]} { 1293 lappend used($keyval) $row 1294 } else { 1295 set used($keyval) [::list $row] 1296 } 1297 } 1298 1299 # Phase II - Merge the collected rows of the second (right) table 1300 # into the map, and eliminate all entries which have no keys in 1301 # the second table. If there is nothing in the left table we 1302 # create an appropriate empty row for the cartesian => definition 1303 # of right outer join. 1304 1305 # We go through used, because map can be empty for outer 1306 1307 foreach k [array names map] { 1308 if {![info exists used($k)]} { 1309 unset map($k) 1310 } 1311 } 1312 foreach k [array names used] { 1313 if {![info exists map($k)]} { 1314 set map($k) [::list [Lrepeatn {} $width]] 1315 } 1316 Cartesian map used $k 1317 } 1318 1319 incr width $w 1320 return [array names map] 1321} 1322 1323proc ::struct::list::MapExtendLeftOuter {mapvar wvar key table} { 1324 upvar $mapvar map $wvar width 1325 array set used {} 1326 1327 ## Keys: All in inner join + additional left keys 1328 ## == All left keys = array names map after 1329 ## all is said and done with it. 1330 1331 # Phase I - Find all keys in the second table matching keys in the 1332 # first. Remember all their rows. 1333 set w [llength [lindex $table 0]] 1334 foreach row $table { 1335 set keyval [lindex $row $key] 1336 if {[info exists map($keyval)]} { 1337 if {[info exists used($keyval)]} { 1338 lappend used($keyval) $row 1339 } else { 1340 set used($keyval) [::list $row] 1341 } 1342 } ; # else: Nothing to do for missing keys. 1343 } 1344 1345 # Phase II - Merge the collected rows of the second (right) table 1346 # into the map. We keep entries which have no keys in the second 1347 # table, we actually extend them <=> Left outer join. 1348 1349 foreach k [array names map] { 1350 if {[info exists used($k)]} { 1351 Cartesian map used $k 1352 } else { 1353 SingleRightCartesian map $k [Lrepeatn {} $w] 1354 } 1355 } 1356 incr width $w 1357 return [array names map] 1358} 1359 1360proc ::struct::list::MapExtendFullOuter {mapvar wvar key table} { 1361 upvar $mapvar map $wvar width 1362 array set used {} 1363 1364 # Phase I - We keep all keys of the right table, even if they are 1365 # missing in the left one <=> Definition of right outer join. 1366 1367 set w [llength [lindex $table 0]] 1368 foreach row $table { 1369 set keyval [lindex $row $key] 1370 if {[info exists used($keyval)]} { 1371 lappend used($keyval) $row 1372 } else { 1373 lappend keylist $keyval 1374 set used($keyval) [::list $row] 1375 } 1376 } 1377 1378 # Phase II - Merge the collected rows of the second (right) table 1379 # into the map. We keep entries which have no keys in the second 1380 # table, we actually extend them <=> Left outer join. 1381 # If there is nothing in the left table we create an appropriate 1382 # empty row for the cartesian => definition of right outer join. 1383 1384 # We go through used, because map can be empty for outer 1385 1386 foreach k [array names map] { 1387 if {![info exists used($k)]} { 1388 SingleRightCartesian map $k [Lrepeatn {} $w] 1389 } 1390 } 1391 foreach k [array names used] { 1392 if {![info exists map($k)]} { 1393 set map($k) [::list [Lrepeatn {} $width]] 1394 } 1395 Cartesian map used $k 1396 } 1397 1398 incr width $w 1399 return [array names map] 1400} 1401 1402## Keyed helpers 1403 1404proc ::struct::list::InitKeyedMap {mapvar wvar table} { 1405 upvar $mapvar map $wvar width 1406 set width [llength [lindex [lindex $table 0] 1]] 1407 foreach row $table { 1408 foreach {keyval rowdata} $row break 1409 if {[info exists map($keyval)]} { 1410 lappend map($keyval) $rowdata 1411 } else { 1412 set map($keyval) [::list $rowdata] 1413 } 1414 } 1415 return [array names map] 1416} 1417 1418proc ::struct::list::MapKeyedExtendInner {mapvar table} { 1419 upvar $mapvar map 1420 array set used {} 1421 1422 # Phase I - Find all keys in the second table matching keys in the 1423 # first. Remember all their rows. 1424 foreach row $table { 1425 foreach {keyval rowdata} $row break 1426 if {[info exists map($keyval)]} { 1427 if {[info exists used($keyval)]} { 1428 lappend used($keyval) $rowdata 1429 } else { 1430 set used($keyval) [::list $rowdata] 1431 } 1432 } ; # else: Nothing to do for missing keys. 1433 } 1434 1435 # Phase II - Merge the collected rows of the second (right) table 1436 # into the map, and eliminate all entries which have no keys in 1437 # the second table. 1438 foreach k [array names map] { 1439 if {[info exists used($k)]} { 1440 Cartesian map used $k 1441 } else { 1442 unset map($k) 1443 } 1444 } 1445 1446 return [array names map] 1447} 1448 1449proc ::struct::list::MapKeyedExtendRightOuter {mapvar wvar table} { 1450 upvar $mapvar map $wvar width 1451 array set used {} 1452 1453 # Phase I - We keep all keys of the right table, even if they are 1454 # missing in the left one <=> Definition of right outer join. 1455 1456 set w [llength [lindex $table 0]] 1457 foreach row $table { 1458 foreach {keyval rowdata} $row break 1459 if {[info exists used($keyval)]} { 1460 lappend used($keyval) $rowdata 1461 } else { 1462 set used($keyval) [::list $rowdata] 1463 } 1464 } 1465 1466 # Phase II - Merge the collected rows of the second (right) table 1467 # into the map, and eliminate all entries which have no keys in 1468 # the second table. If there is nothing in the left table we 1469 # create an appropriate empty row for the cartesian => definition 1470 # of right outer join. 1471 1472 # We go through used, because map can be empty for outer 1473 1474 foreach k [array names map] { 1475 if {![info exists used($k)]} { 1476 unset map($k) 1477 } 1478 } 1479 foreach k [array names used] { 1480 if {![info exists map($k)]} { 1481 set map($k) [::list [Lrepeatn {} $width]] 1482 } 1483 Cartesian map used $k 1484 } 1485 1486 incr width $w 1487 return [array names map] 1488} 1489 1490proc ::struct::list::MapKeyedExtendLeftOuter {mapvar wvar table} { 1491 upvar $mapvar map $wvar width 1492 array set used {} 1493 1494 ## Keys: All in inner join + additional left keys 1495 ## == All left keys = array names map after 1496 ## all is said and done with it. 1497 1498 # Phase I - Find all keys in the second table matching keys in the 1499 # first. Remember all their rows. 1500 set w [llength [lindex $table 0]] 1501 foreach row $table { 1502 foreach {keyval rowdata} $row break 1503 if {[info exists map($keyval)]} { 1504 if {[info exists used($keyval)]} { 1505 lappend used($keyval) $rowdata 1506 } else { 1507 set used($keyval) [::list $rowdata] 1508 } 1509 } ; # else: Nothing to do for missing keys. 1510 } 1511 1512 # Phase II - Merge the collected rows of the second (right) table 1513 # into the map. We keep entries which have no keys in the second 1514 # table, we actually extend them <=> Left outer join. 1515 1516 foreach k [array names map] { 1517 if {[info exists used($k)]} { 1518 Cartesian map used $k 1519 } else { 1520 SingleRightCartesian map $k [Lrepeatn {} $w] 1521 } 1522 } 1523 incr width $w 1524 return [array names map] 1525} 1526 1527proc ::struct::list::MapKeyedExtendFullOuter {mapvar wvar table} { 1528 upvar $mapvar map $wvar width 1529 array set used {} 1530 1531 # Phase I - We keep all keys of the right table, even if they are 1532 # missing in the left one <=> Definition of right outer join. 1533 1534 set w [llength [lindex $table 0]] 1535 foreach row $table { 1536 foreach {keyval rowdata} $row break 1537 if {[info exists used($keyval)]} { 1538 lappend used($keyval) $rowdata 1539 } else { 1540 lappend keylist $keyval 1541 set used($keyval) [::list $rowdata] 1542 } 1543 } 1544 1545 # Phase II - Merge the collected rows of the second (right) table 1546 # into the map. We keep entries which have no keys in the second 1547 # table, we actually extend them <=> Left outer join. 1548 # If there is nothing in the left table we create an appropriate 1549 # empty row for the cartesian => definition of right outer join. 1550 1551 # We go through used, because map can be empty for outer 1552 1553 foreach k [array names map] { 1554 if {![info exists used($k)]} { 1555 SingleRightCartesian map $k [Lrepeatn {} $w] 1556 } 1557 } 1558 foreach k [array names used] { 1559 if {![info exists map($k)]} { 1560 set map($k) [::list [Lrepeatn {} $width]] 1561 } 1562 Cartesian map used $k 1563 } 1564 1565 incr width $w 1566 return [array names map] 1567} 1568 1569proc ::struct::list::Dekey {keyedtable} { 1570 set table [::list] 1571 foreach row $keyedtable {lappend table [lindex $row 1]} 1572 return $table 1573} 1574 1575# ::struct::list::Lswap -- 1576# 1577# Exchange two elements of a list. 1578# 1579# Parameters: 1580# listvar Name of the variable containing the list to manipulate. 1581# i, j Indices of the list elements to exchange. 1582# 1583# Results: 1584# The modified list 1585# 1586# Side effects: 1587# None 1588 1589proc ::struct::list::Lswap {listvar i j} { 1590 upvar $listvar list 1591 1592 if {($i < 0) || ($j < 0)} { 1593 return -code error {list index out of range} 1594 } 1595 set len [llength $list] 1596 if {($i >= $len) || ($j >= $len)} { 1597 return -code error {list index out of range} 1598 } 1599 1600 if {$i != $j} { 1601 set tmp [lindex $list $i] 1602 lset list $i [lindex $list $j] 1603 lset list $j $tmp 1604 } 1605 return $list 1606} 1607 1608# ::struct::list::Lfirstperm -- 1609# 1610# Returns the lexicographically first permutation of the 1611# specified list. 1612# 1613# Parameters: 1614# list The list whose first permutation is sought. 1615# 1616# Results: 1617# A modified list containing the lexicographically first 1618# permutation of the input. 1619# 1620# Side effects: 1621# None 1622 1623proc ::struct::list::Lfirstperm {list} { 1624 return [lsort $list] 1625} 1626 1627# ::struct::list::Lnextperm -- 1628# 1629# Accepts a permutation of a set of elements and returns the 1630# next permutatation in lexicographic sequence. 1631# 1632# Parameters: 1633# list The list containing the current permutation. 1634# 1635# Results: 1636# A modified list containing the lexicographically next 1637# permutation after the input permutation. 1638# 1639# Side effects: 1640# None 1641 1642proc ::struct::list::Lnextperm {perm} { 1643 # Find the smallest subscript j such that we have already visited 1644 # all permutations beginning with the first j elements. 1645 1646 set len [expr {[llength $perm] - 1}] 1647 1648 set j $len 1649 set ajp1 [lindex $perm $j] 1650 while { $j > 0 } { 1651 incr j -1 1652 set aj [lindex $perm $j] 1653 if { [string compare $ajp1 $aj] > 0 } { 1654 set foundj {} 1655 break 1656 } 1657 set ajp1 $aj 1658 } 1659 if { ![info exists foundj] } return 1660 1661 # Find the smallest element greater than the j'th among the elements 1662 # following aj. Let its index be l, and interchange aj and al. 1663 1664 set l $len 1665 while { $aj >= [set al [lindex $perm $l]] } { 1666 incr l -1 1667 } 1668 lset perm $j $al 1669 lset perm $l $aj 1670 1671 # Reverse a_j+1 ... an 1672 1673 set k [expr {$j + 1}] 1674 set l $len 1675 while { $k < $l } { 1676 set al [lindex $perm $l] 1677 lset perm $l [lindex $perm $k] 1678 lset perm $k $al 1679 incr k 1680 incr l -1 1681 } 1682 1683 return $perm 1684} 1685 1686# ::struct::list::Lpermutations -- 1687# 1688# Returns a list containing all the permutations of the 1689# specified list, in lexicographic order. 1690# 1691# Parameters: 1692# list The list whose permutations are sought. 1693# 1694# Results: 1695# A list of lists, containing all permutations of the 1696# input. 1697# 1698# Side effects: 1699# None 1700 1701proc ::struct::list::Lpermutations {list} { 1702 1703 if {[llength $list] < 2} { 1704 return [::list $list] 1705 } 1706 1707 set res {} 1708 set p [Lfirstperm $list] 1709 while {[llength $p]} { 1710 lappend res $p 1711 set p [Lnextperm $p] 1712 } 1713 return $res 1714} 1715 1716# ::struct::list::Lforeachperm -- 1717# 1718# Executes a script for all the permutations of the 1719# specified list, in lexicographic order. 1720# 1721# Parameters: 1722# var Name of the loop variable. 1723# list The list whose permutations are sought. 1724# body The tcl script to run per permutation of 1725# the input. 1726# 1727# Results: 1728# The empty string. 1729# 1730# Side effects: 1731# None 1732 1733proc ::struct::list::Lforeachperm {var list body} { 1734 upvar $var loopvar 1735 1736 if {[llength $list] < 2} { 1737 set loopvar $list 1738 # TODO run body. 1739 1740 # The first invocation of the body, also the last, as only one 1741 # permutation is possible. That makes handling of the result 1742 # codes easier. 1743 1744 set code [catch {uplevel 1 $body} result] 1745 1746 # decide what to do upon the return code: 1747 # 1748 # 0 - the body executed successfully 1749 # 1 - the body raised an error 1750 # 2 - the body invoked [return] 1751 # 3 - the body invoked [break] 1752 # 4 - the body invoked [continue] 1753 # everything else - return and pass on the results 1754 # 1755 switch -exact -- $code { 1756 0 {} 1757 1 { 1758 return -errorinfo [ErrorInfoAsCaller uplevel foreachperm] \ 1759 -errorcode $::errorCode -code error $result 1760 } 1761 3 {} 1762 4 {} 1763 default { 1764 # Includes code 2 1765 return -code $code $result 1766 } 1767 } 1768 return 1769 } 1770 1771 set p [Lfirstperm $list] 1772 while {[llength $p]} { 1773 set loopvar $p 1774 1775 set code [catch {uplevel 1 $body} result] 1776 1777 # decide what to do upon the return code: 1778 # 1779 # 0 - the body executed successfully 1780 # 1 - the body raised an error 1781 # 2 - the body invoked [return] 1782 # 3 - the body invoked [break] 1783 # 4 - the body invoked [continue] 1784 # everything else - return and pass on the results 1785 # 1786 switch -exact -- $code { 1787 0 {} 1788 1 { 1789 return -errorinfo [ErrorInfoAsCaller uplevel foreachperm] \ 1790 -errorcode $::errorCode -code error $result 1791 } 1792 3 { 1793 # FRINK: nocheck 1794 return 1795 } 1796 4 {} 1797 default { 1798 return -code $code $result 1799 } 1800 } 1801 set p [Lnextperm $p] 1802 } 1803 return 1804} 1805 1806proc ::struct::list::ErrorInfoAsCaller {find replace} { 1807 set info $::errorInfo 1808 set i [string last "\n (\"$find" $info] 1809 if {$i == -1} {return $info} 1810 set result [string range $info 0 [incr i 6]] ;# keep "\n (\"" 1811 append result $replace ;# $find -> $replace 1812 incr i [string length $find] 1813 set j [string first ) $info [incr i]] ;# keep rest of parenthetical 1814 append result [string range $info $i $j] 1815 return $result 1816} 1817 1818# ### ### ### ######### ######### ######### 1819## Ready 1820 1821namespace eval ::struct { 1822 # Get 'list::list' into the general structure namespace. 1823 namespace import -force list::list 1824 namespace export list 1825} 1826package provide struct::list 1.7 1827