3
$\begingroup$

I have a list of boolean functions {f1, f2, f3, ...} that take a value and return True or False. They are used to check whether the value meets certain conditions.

I need to run construct a nested If statement that for an arbitrary value sequentially evaluates the boolean functions above and returns True if all functions return True, and False otherwise :

If[f1[#],
  If[f2[#],
    If[f3[#],
      True,
      False],
    False],
  False]

It is important that this evaluation is sequetial, so that f1 is evaluated before f2, f2 before f3 etc.; and that as soon as any of the functions returns False, the entire evaluation stops. This is necessary because each evaluation is time-consuming, and I don't want to evaluate any other functions once one of them returns False.

I wrote the following function to generate the nested if statement:

 NestedIf[conds_] := Function[d, Fold[If[#2[d], #1, False] &, True, Reverse@conds]];

And use it within Select statement to pick out the values from my dataset that meet all of the conditions f1,f2,f3, etc.:

Select[data, NestedIf[{f1, f2, f3}]]

However, the problem with this function is that once I pass it a value, it first evaluates all conditions and only then inserts them into the nested If structure. So I get no time savings.

I tried using SetAttributes[NestedIf,HoldAll], but that did not help because the arguments are still evaluated first.

How can I prevent the arguments from being evaluated prematurely?

P.S. AllTrue command does something very similar to what I want, by sequentially checking if all values in a list satisfies one test. What I need is to test whether one value satisfies a list of tests.

P.P.S. Several people have suggested using And for sequential evaluation. Unfortunately the problem is that all the arguments ends up being evaluated before And begins sequential evaluation. For example, try running this:

 f1[x_] := (Print["First"]; x > 10);
 f2[x_] := (Print["Second"]; x > 20);

 And[Through[{f1, f2}[#]]] & @ 5

The output will be:

 First
 Second
 {False, False}

So both arguments are evaluated before they are passed to And, and I'm trying to find a way to prevent that.

$\endgroup$
  • 1
    $\begingroup$ See Which[] and AllTrue[] and And[]. $\endgroup$ – David G. Stork Apr 7 '15 at 21:04
  • 3
    $\begingroup$ And will evaluate its arguments sequentially, returning False as soon as any argument is False. $\endgroup$ – Simon Woods Apr 7 '15 at 21:19
  • 1
    $\begingroup$ "And evaluates all tests at the same time rather than sequentially" <-- This is not correct. There's no parallel evaluation. Arguments are checked one by one until one returns False or until all are done. $\endgroup$ – Szabolcs Apr 7 '15 at 23:57
  • 1
    $\begingroup$ @SimonWoods, @Szabolcs, @kguler To clarify what I mean, the problem is not that And itself doesn't evaluate arguments sequentially, but that by the time it starts sequential evaluation, all arguments have already been evaluated. Through@{conds}@x evaluates all arguments before passing them to And $\endgroup$ – verse Apr 8 '15 at 0:02
  • 2
    $\begingroup$ Re your update: the correct way to use And is And[arg1, arg2, arg3] and not And[{arg1, arg2, arg2}]. Observe: And[Print[1]; False, Print[2]; False], more conveniently written as (Print[1]; False) && (Print[2]; False). The second argument never gets evaluated. $\endgroup$ – Szabolcs Apr 8 '15 at 0:18
5
$\begingroup$

You need to construct your desired expression without unwanted evaluation of its constituent parts. Here is one approach to do that:

makeTest[{tests__}] := Replace[And[tests] &, fn_ :> fn[#], {2}]

This works because the surrounding Function prevents evaluation before and after the Replace operation.

Example:

f1[x_] := (Print["First"]; x > 10);
f2[x_] := (Print["Second"]; x > 20);

myTest = makeTest[{f1, f2}]
f1[#1] && f2[#1] &

Use myTest as needed.


Another method alluded to in the comments is to use Fold primarily. I shall combine this with a SubValues definition (see: Define parameterized function):

testMulti[tests_List][expr_] := Fold[# && #2[expr] &, True, tests]

testMulti[{f1, f2}][7]

First

False

Also:

Block[{Print},
  Range[15, 25] ~Select~ testMulti[{f1, f2}]
]
{21, 22, 23, 24, 25}

The Block is just to keep Print from firing in this example.

$\endgroup$
  • $\begingroup$ This is incredible! $\endgroup$ – verse Apr 8 '15 at 1:02
  • $\begingroup$ @verse Thanks, and I'm glad I could help. $\endgroup$ – Mr.Wizard Apr 8 '15 at 1:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.