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