- 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 DuoasRS 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}
