source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/struct/list.tcl

devel
Last change on this file was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

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