List Comprehension is sort-of regex (in reverse) for lists: sort of a short hand way to construct lists from other lists. Playing Haskell has an interesting Tcl take on list comprehensions. Here is another:
# listc - (List Comprehension) constructs a list from an 'expression' # operating on items from supplied lists. # # Synopsis: # listc expression vars1 <- list1 [.. varsN <- listN] [condition] # # where # 'expression' is any Tcl expression (cmd + args) returning data to populate # the list. This expression is eval'd. # # 'vars1' is a list of variable identifier representing items from 'list1'. # The variables are available for use in 'condition' and 'expression'. # # '<-' is merely syntatical sugar (Haskell). # # 'list1' is the source list. # # 'condition' is any Tcl expression (cmd + args) returning 1/ 0 or true/false. # The condition string is eval'd. # # You can supply any number of varsN <- listN triplets. They are applied as # foreach loops, nested as ordered. 'Condition' is eval'd inside the last # triplet's loop context. # proc listc {expression var1 <- list1 args} { set res {} # Conditional expression (if not supplied) is always 'true'. # set condition {expr 1} # We should at least have one var/list pair. # lappend var_list_pairs $var1 $list1 # Collect any additional var/list pairs. # while {[llength $args] >= 3} { lappend var_list_pairs [lindex $args 0] # skip "<-" lappend var_list_pairs [lindex $args 2] set args [lrange $args 3 end] } # If there are no more triplets and at least 1 arg left... # if {[llength $args] > 0} { # Grab condition # set condition [lindex $args 0] } # Build the foreach commands (for each var/list pair). # foreach {var list} $var_list_pairs { append foreachs "foreach \{$var\} \{$list\} \{" } # Insert the conditional expression. # append foreachs {if {[eval $condition]} {lappend res [eval $expression]}} # For each foreach, make sure we terminate it with a closing brace. # foreach {var list} $var_list_pairs { append foreachs \} } # Evaluate the foreachs... # eval $foreachs return $res }
Here are some examples:
set i [list 1 2 3 4 5 6] set l2 [listc {set i} i <- $i] puts "\tA copy of the list: $l2"
A copy of the list: 1 2 3 4 5 6
set dbl [listc {expr $n*2} n <- $i] puts "\tDouble values from list: $dbl"
Double values from list: 2 4 6 8 10 12
set evn [listc {set i} i <- $i {expr {$i%2 == 0}}] puts "\tOnly even numbers: $evn"
Only even numbers: 2 4 6
proc digits {str} { set lstr [split $str ""] return [listc {set d} d <- $lstr {string is digit $d}] } puts "Just digits from (703)-999-0012= [digits (703)-999-0012]"
Just digits from (703)-999-0012= 7 0 3 9 9 9 0 0 1 2
set names1 [list Todd Coram Bob Jones Tim Druid] set lf [listc {list "$l,$f"} {f l} <- $names1] puts "From ($names1)\n\tLast,first = $lf"
From (Todd Coram Bob Jones Tim Druid) Last,first = Coram,Todd Jones,Bob Druid,Tim
set l3 [listc {set f} {f l} <- $names1 {string match "T*" $f}] puts "From ($names1)\n\tOnly names starting with 't': $l3"
From (Todd Coram Bob Jones Tim Druid) Only names starting with 't': Todd Tim
Now, let's get fancy. Given a proc like lisp's setp and a simple lambda:
proc lset {vars {values {}}} { if {$values == {}} { foreach var $vars { lappend values [uplevel set $var] } return $values } uplevel [list foreach $vars $values break] return $values } proc lambda {arglst body} { set name [string map {" " _ $ _} [info level 0]] proc $name $arglst $body set name }
You can get even more functional:
set names2 [list {Todd Coram} {Bob Jones} {Tim Druid}] set lf [listc {[lambda nm {lset {f l} $nm; return "$l,$f"}] $n} n <- $names2] puts "From ($names2)\n\tAnother last,first = $lf"
From ({Todd Coram} {Bob Jones} {Tim Druid}) Another last,first = Coram,Todd Jones,Bob Druid,Tim
This listc proc also supports multiple (nesting) list variables:
set l4 [listc {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}: \n\t$l4"
Create a matrix pairing {a b c} and {1 2 3}: {a 1} {a 2} {a 3} {b 1} {b 2} {b 3} {c 1} {c 2} {c 3}
-- Todd Coram