- filter var list expr
% filter x {1 2 3 4 5 6 7 8 9} {$x > 4} 5 6 7 8 9 % filter x {1 2 3 4 5 6 7 8 9} {$x % 3} 1 2 4 5 7 8 % filter x {1 2 3 4 5 6 7 8 9} {($x % 3) == 0} 3 6 9 % filter x [list a b {} c d e {} {} f g] {[llength $x]} a b c d e f gDo you need filter? If you can see the following pattern in your code, you need it:
set res {} foreach x $list { if {[foobar $x]} { lappend res $x } } use $resFinally the (very simple) code:
proc filter {fvar flist fexpr} { upvar 1 $fvar var set res {} foreach var $flist { set varCopy $var if {[uplevel 1 [list expr $fexpr]]} { lappend res $varCopy } } return $res }
RS would prefer an argument sequence with the list in the end:
filter x {$x > 4} $listso the second and third argument, which form a lambda, are left together.
SS Indeed, it's not clear what's the best. I like more what you proposed from an aesthetic point of view, but to have the same arguments order as map and foreach can be a good point.There is another option too:
filter $list x {$x > 4}that sounds to me slightly better.
Duoas writes his version
proc lfilter {ls varName script} upvar 1 $varName var set result {} foreach var $ls val $ls { if {[uplevel 1 $script]} { lappend result $val } } return $result }and his motive (no expr)
% set mixed {1 abc 2.5 twelve Annie -3 0} % lfilter $mixed x {string is double -strict $x} 1 2.5 -3 0Hope this helps.AM (21 june 2006) But that would fail in circumstances where a numeric conditions is needed - "$x > 4" would need to become "expr {$x > 4}", not a pleasant user requirement, IMOdbohdan 2014-06-01: Changed uplevel 1 eval $script to uplevel 1 $script in lfilter above. The eval was unnecessary and broke quoting.
True, but I'm still not so sure I'm convinced either way. I don't like being constrained. In any case, with the advent of apply in Tcl 8.5, we can use true lambdas instead of the versions we've seen so far:
proc ::filter {predicate ls} { set result {} foreach el $ls { if {[eval apply [list $predicate] $el]} { lappend result $el } } return $result }This version is particularly capable because you can choose between lambdas taking each element in the list as a whole or as individual arguments:
% filter {{a b} {expr {$a == $b}}} {{2 2} {3 -7} {42 9} {7 7}} {2 2} {7 7} % filter {args {expr {[llength $args] == 2}}} {one {two words} only one {three words here} {two again}} {two words} {two again}01-05-2007 Duoas
RS Again, here a version that uses {*}:
proc ::filter {predicate ls} { set result {} foreach el $ls { if [apply $predicate {*}$el] {lappend result $el} } return $result }These filters using apply look interesting from a theoretical point of view, but they also seem impractical when compared to the expr versions. There are two reasons: One, expressions are the most commonly form of predicate. Two, it's easy to run a script from within an expression, but it's a bit more difficult to run an expression from within a script. Let me show you:
# Arbitrary script predicate: filter_lambda {{x} {string is double -strict $x}} {a b c 1 2 3} filter_expr {x} {[string is double -strict $x]} {a b c 1 2 3} # Expression predicate: filter_lambda {{x} {expr {$x > 5}}} {1 2 3 4 5 6 7 8 9} filter_expr {x} {$x > 5} {1 2 3 4 5 6 7 8 9}The [filter_expr] procs I demoed take a list of variables into which each element is unpacked, same as the filters by Duoas and RS. My point is that [filter_expr] takes less typing in the common case and the same amount of typing in the (rarer?) arbitrary script predicate case.
AMG: Using lcomp:
proc filter {pred list} { lcomp {$x} for x in $list if $pred }Each element in $list is assigned to the variable $x which should be tested by $pred, which is an expression.
% filter {$x > 5} {1 2 3 4 5 6 7 8 9} 6 7 8 9If you need to unpack elements into multiple variables, like in Duoas's example, you can flatten the list. But you'll have to use lcomp directly; my filter isn't flexible enough.
% set list {{2 2} {3 -7} {42 9} {7 7}} # Duoas's example: % filter {{a b} {expr {$a == $b}}} $list {2 2} {7 7} # My example: % lcomp {[list $a $b]} for {a b} in [concat {*}$list] if {$a == $b} {2 2} {7 7}Actually there is a way, but it's complicated.
% filter {[apply {{a b} {expr {$a == $b}}} {*}$x]} $list {2 2} {7 7}As for Duoas's other example, it's better not to do any special tricks.
set list {one {two words} only one {three words here} {two again}} # Duoas's example: % filter {args {expr {[llength $args] == 2}}} $list {two words} {two again} # My example: % filter {[llength $x] == 2} $list {two words} {two again}