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