Version 13 of lcomp

Updated 2005-11-04 20:38:04

AMG I wasn't quite satisfied with [listc], Todd Coram's list comprehension procedure, so I adapted it into something far worse. Behold!

 proc lcomp {expression args} {
     # Check the number of arguments.
     if {[llength $args] < 2} {
         error "wrong # args: should be \"lcomp expression var1 list1\
             ?... varN listN? ?condition?\""
     }

     # Extract condition from $args, or use default.
     if {[llength $args] % 2 == 1} {
         set condition [lindex $args end]
         set args [lrange $args 0 end-1]
     } else {
         set condition 1
     }

     # Collect all var/list pairs and store in reverse order.
     set varlst [list]
     foreach {var lst} $args {
         set varlst [concat [list $var] [list $lst] $varlst]
     }          

     # Actual command to be executed, repeatedly.
     set script {lappend result [subst $expression]}

     # If necessary, make $script conditional.
     if {$condition ne "1"} {
         set script [list if {[expr $condition]} $script]
     }

     # Apply layers of foreach constructs around $script.
     foreach {var lst} $varlst {
         set script [list foreach $var $lst $script]
     }

     # Do it!
     set result [list]
     {expand}$script ;# Change to "eval $script" if using Tcl 8.4 or older.
     return $result
 }

Mainly I didn't like [listc]'s method of constructing its $foreachs variable (which I have named $script in my code), so I switched it around to be built inside-out rather than left-to-right. I start by putting together the actual command that'll be run by the tangled nest of [foreach] constructs; then I tack on [foreach]s and [if]s with the aid of [list]. Magically I no longer have to worry about matching braces, thus shortening my code into the sorry heap you see above.

Next up, I changed [eval] to [subst] and [expr], since I felt that all $expressions would use substitution and all $conditions would be boolean expressions. Especially nice is the ability to use [...] substitutions with both [subst] and [expr], so the old behavior can be had by wrapping the parameter with [ and ].

Lastly (unless I missed something), I dropped the <- sugar. I didn't much care for it, since [foreach] itself doesn't use it. You can re-add it if you like by changing "% 2" to "% 3" and "< 2" to "< 3" and the first instance of "var lst" to "var <- lst".


Examples? I'll start by listing Todd's examples modified to work with [lcomp].

 set i  [list 1 2 3 4 5 6]
 set l2 [lcomp {$i} i $i]
 puts "A copy of the list: $l2"

 set dbl [lcomp {[expr {$n*2}]} n $i]
 puts "Double values from list: $dbl"

 set evn [lcomp {$i} i $i {$i%2 == 0}]
 puts "Only even numbers: $evn"

 proc digits {str} {
     set lstr [split $str ""]
     return [lcomp {$d} d $lstr {[string is digit $d]}]
 }
 puts "Just digits from (703)-999-0012= [digits (703)-999-0012]"

 set names1 [list Todd Coram Bob Jones Tim Druid]
 set lf [lcomp {$l,$f} {f l} $names1]
 puts "From ($names1): Last,first = $lf"

 set l3 [lcomp {$f} {f l} $names1 {[string match "T*" $f]}]
 puts "From ($names1): Only names starting with 't': $l3"

 set l4 [lcomp {[list $n1 $n2]} n1 [list a b c] n2 [list 1 2 3]]
 puts "Create a matrix pairing {a b c} and {1 2 3}: $l4"

Here are some more:

 lcomp {$x} x {0 1 2 3}                           ;# 0 1 2 3
 lcomp {$y $x} {x y} {0 1 2 3}                    ;# {1 0} {3 2}
 lcomp {[expr {$x ** 2}]} x {0 1 2 3}             ;# 0 1 4 9
 lcomp {[expr {$x + $y}]} x {0 1 2 3} y {0 1 2 3} ;# 0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6
 lcomp {$x} x {0 1 2 3} {$x % 2 == 0}             ;# 0 2

Practical uses? The following deletes all images named by any elements in the images array:

 image delete {expand}[lcomp {$val} {key val} [array get images]]

I'll come up with more later as I use [lcomp] more in my own code.


AMG: It's possible to modify [lcomp] to use [eval] to process $expression rather than [subst] or [expr]. [subst]-like behavior can be had with the [K*] proc found on K. Single-argument [list] or [concat] should also work. For instance,

 lcomp {K* $x} x {0 1 2 3}                        ;# 0 1 2 3
 lcomp {concat $y $x} {x y} {0 1 2 3}             ;# {1 0} {3 2}
 lcomp {expr {$x ** 2}} x {0 1 2 3}               ;# 0 1 4 9
 lcomp {expr {$x + $y}} x {0 1 2 3} y {0 1 2 3}   ;# 0 1 2 3 1 2 3 4 2 3 4 5 3 4 5 6
 lcomp {K* $x} x {0 1 2 3} {$x % 2 == 0}          ;# 0 2

For the moment at least, there's a bit of list comprehension discussion at the bottom of [for]. Some day (soon?) those ideas will migrate to this page.


See also [list].


[ Category Command | Category Concept | Arts and crafts of Tcl-Tk programming ]