# filter

Definition: | sequence res = filter(sequence s, rid_string rs, object userdata={}, string rangetype="") -- or -- integer res = filter_count(<ditto>)
Description: | Select only those elements from a sequence that pass a specified test. s: the sequence to be examined rs: if string, one of the 9 builtin tests, otherwise (the routine_id of) a function that implements the test userdata: a range, set, pivot, or object optionally passed to said function rangetype: for rs="in"/"out" only, inclusive/exclusive qualifiers or "" for subset handling. filter() returns a sequence containing some subset of the elements originally provided in s, which could possibly be {} aka none. filter_count() returns the same as length(filter()), but without constructing and discarding an intermediate sequence, with obvious reduced memory and therefore increased performance implications, however you should be aware the compiler may be able to optimise a specific longhand loop in ways it cannot hope to match with the more generic code inside filter(), should performance be critical.
pwa/p2js: | Supported.
Comments: | You can supply your own test (as a routine_id) or use one of the nine built-ins ("<", "<=", "=", "!=", ">=", ">", "notbits", "in", or "out"). ## built-ins (string rs) As well as those nine just listed, the aliases "lt","le","eq","==","ne","ge","gt" may also be used (must be lowercase). When rs is "in" or "out", rangetype can be one of five values: {"[]","[)","(]","()",""}. For the first four cases userdata must be a sequence of two (lo/hi) values, with [|] indicating inclusive, (|) exclusive. For the fifth case, userdata is a set, and elements are kept depending on whether they are found in that set or not. When rs is one of the string or bit comparison ops, userdata is the value to test against, some examples are given below. See test/t65filter.exw for a comprehensive test set covering all known/supported uses of filter(). It may (or may not) contain some additional edge cases that have arisen since this documentation was written and that way perhaps answer any remaining questions you may have about how all these built-ins behave (ditto for user-provided functions). ## supplied function (integer fn := rs) fn should be a function that accepts at least one parameter. The optional userdata value of {} is only passed to fn if it needs it: should fn have no second parameter or the second parameter is optional (already has a default), it will not get passed the above default. If fn accepts/needs 3 parameters it will get (s[i],i,s). Obviously if you pass a non-{} value that disagrees in any way with fn you will get an error. In contrast, and again obviously, even if the first parameter of fn is defaulted it will still get passed each s[i] in turn. fn() is expected to return a true/false result indicating whether to keep or discard each element. fn() can accept 1, 2, or 3 parameters. Without any optional parameters, when userdata is {} it is invoked as f(s[i]), f(s[i],i), or f(s[i],i,s) respectively, otherwise it is invoked as f(s[i],userdata) or f(s[i],i,userdata). The presence of any optional parameters basically encourages filter() to pass the fewest parameters possible, but always >=1. In that way, fn() can be recursive and not have done=0 or depth=1 style parameters clobbered by the builtin filter(), though it may end up wiser/necessary to split out any such recursive parts/defaulted args once things outgrow trivial. This handling of defaulted parameters is also consistent with apply(), though for different and perhaps slightly less convincing reasons. The rangetype parameter is not used when rs is not "in" or "out", and in fact an error occurs if it is not "".
Examples: |
    -- Using user-defined or builtin functions:
    constant data = {5,8,20,19,3,2,10}
    ?filter(data, even)         -- ==> {8, 20, 2, 10}
    ?filter(data, odd)          -- ==> {5, 19, 3}
    -- Using 'in' and 'out' with a set (same data as above):
    ?filter(data, "in",  {3,4,5,6,7,8}) -- ==> {5,8,3}
    ?filter(data, "out", {3,4,5,6,7,8}) -- ==> {20,19,2,10}
    -- Using 'in' and 'out' with inclusive/exclusive ranges:
    ?filter(data, "in",  {3,8}, "[]") -- (aka >=3 & <=8) -- ==> {5,8,3}
    ?filter(data, "in",  {3,8}, "[)") -- (aka >=3 & <8)  -- ==> {5,3}
    ?filter(data, "in",  {3,8}, "(]") -- (aka >3 & <=8)  -- ==> {5,8}
    ?filter(data, "in",  {3,8}, "()") -- (aka >3 & <8)   -- ==> {5}
    ?filter(data, "out", {3,8}, "[]") -- (NOT >=3 & <=8) -- ==> {20,19,2,10}
    ?filter(data, "out", {3,8}, "[)") -- (NOT >=3 & <8)  -- ==> {8,20,19,2,10}
    ?filter(data, "out", {3,8}, "(]") -- (NOT >3 & <=8)  -- ==> {20,19,3,2,10}
    ?filter(data, "out", {3,8}, "()") -- (NOT >3 & <8)   -- ==> {8,20,19,3,2,10}
    -- Using "notbits", aka not and_bits(si,userdata):
    ?filter({#1,#2,#4,#8,#10,#20},"notbits",#15) -- ==> {2,8,32}
    -- Using the built-in comparators (not particularly efficiently here, mind you):
    function quiksort(sequence s)
        if length(s)<=1 then
            return s
        end if
        atom s1 = s[1]
        s = s[2..$]
        return quiksort(filter(s, "<=", s1)) & s1 & quiksort(filter(s, ">", s1))
    end function
    ?quiksort({5,4,7,2,4,9,1,0,4,32,7,54,2,5,8,445,67})
    ------==> {0,1,2,2,4,4,4,5,5,7,7,8,9,32,54,67,445}
Implementation: | See builtins\pFilter.e (an autoinclude) for details of the actual implementation.
See Also: | apply
Technicalia | Compatibility with Euphoria: When rs is a user-supplied routine_id, it must acccept exactly two non-defaulted parameters. Euphoria does not support "notbits", and requires `routine_id("mask_nums")` rather than just `mask_nums`, plus there is no filter_count() or equivalent function, but otherwise it should be fully compatible. Not all of these options will necessarily make perfect sense in all cases, for instance something like find_all() might be better in that it delivers a more actionable result. Judicious use of functional programming style techniques can both shorten code and greatly improve clarity. Careful design can completely eliminate many laboriously hand-written looping constructs, for just one example. Equally, however, pig-headed adherence to "pure functions" and excessive nesting can produce quite unreadable and almost impossible to maintain software. The simple act of regularly saving intermediate results in appropriately named local variables will often make code easier to follow and debugging ten times easier, although admittedly self-clobbering (ie/eg `res = f(res)`) can sometimes improve performance significantly, but usually at some small cost to overall clarity and therefore debugging.