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