Drools: how to iterate over a list and check if all elements apply a condition

Viewed 40

I have the next drools rules that checks if a student has at least two grades and average for each discipline is at least 5.

rule "student with an average of 5 per discipline and at least two grades" salience 7
    when
     $student : Student(grades.size() >= 2)
     $list : Object() from computeAverageGrade($student)
     $value : Double() from $list
     Boolean(booleanValue() == true) from $value >= 5.0
    then
     System.out.println(3);
    student.setValid(true);
end

rule "student with at least two grades and average less than 5" salience 8
    when
        $student : Student(grades.size() >= 2)
        $list : Object() from computeAverageGrade($student)
        $value : Double() from $list
        Boolean(booleanValue() == true) from $value < 5.0
    then
     System.out.println(2);
    student.setValid(false);
end

The thing is whenever I try to execute a drools rule the list returned from computeAverageGrade checks each grade against every rule. So if i had a student with these averages [2.0, 9.5, 10.0, 4.5]. The rules would execute like this 2 2 3 3 and i don't understand the behaviour.

I tried replacing the rules with the accumulate function from drools but i keep getting errors and i suspect i am using it wrong.

How should i rewrite these rules such as the rule would get these averages [2.0, 9.5, 10.0, 4.5] and only execute the 2nd rule

1 Answers

Your output, as described, makes sense. Especially if computeAverageGrade is actually returning a List instead of an Object and your rules are just written unclearly.

I'm going to just consider one rule for this explanation because the same thing is happening in both.

These are your conditions:

$student : Student(grades.size() >= 2)
$list : Object() from computeAverageGrade($student)
$value : Double() from $list
Boolean(booleanValue() == true) from $value >= 5.0

These are your conditions rewritten to be less convoluted:

$student: Student( grades.size() >= 2 )
$list: ArrayList() from computeAverageGrade($student)
$value: Double( this >= 5.0 ) from $list

The first line pulls a Student instance out of working memory and assigns it to the variable $student. If there is one student in working memory, this line will only ever refer to that one student. If there are multiple students, then this rule will trigger once per student because of this line.

The second line calls an external function (either defined in Java somewhere or declared in the DRL itself) and assigns the result of that call to $list. Based on your previous question, and the variable name, I assume that this is actually returning a List of doubles instead of a single value even though the name of the method implies that it should be returning a single value.

The third line (third and fourth in the original) is where you're getting confused. The rule will trigger once for each value that meets this condition.

So if a student has averages [2.0, 9.5, 10.0, 4.5], then this line/s evaluates [false, true, true, false]. The rule will then fire twice: once for the 9.5 and once for the 10.0 values.


How to fix it depends on what your rule is actually supposed to be doing. You've not provided your actual use case, but I can make some guesses.

If you want the rule to fire only if all of the values in $list are >= 5.0, then you'd change your rule to this:

rule "all averages >= 5.0"
salience 7
when
  $student: Student(grades.size() >= 2)
  $list: ArrayList() from computeAverageGrade($student)
  not( Double( this < 5.0 ) from $list )
then
  //...
end

This version will fire exactly once if all of the values in $list are >= 5.0. It does this by checking that there doesn't exist a value that's less than 5.0. (That's the not check at the end of the conditions.)

This version of the rule will not fire for the example inputs provided, because all of the values do not meet the criteria.

Alternatively, if you want it to fire as long as there exists at least one value >= 5.0, you'd do like this:

rule "at least one >= 5.0"
salience 7
when
  $student: Student(grades.size() >= 2)
  $list: ArrayList() from computeAverageGrade($student)
  exists( Double( this >= 5.0 ) from $list )
then
  // ...
end

This version of the rule will fire exactly once, as long as at least 1 value in $list is >= 5.0. This version of the rule will fire for your example inputs, because there are 2 values that meet the criteria.

Finally, if you actually want to figure out if the average value (mathematical "mean") of the values in $list are 5.0 or greater, then you'd do like this:

rule "average >= 5.0"
when
  $student: Student(grades.size() >= 2)
  $list: ArrayList() from computeAverageGrade($student)
  Double( this >= 5.0 ) from accumulate(
    $d: Double() from $list,
    average($d)
  )
then
  // ...
end

Here, I use accumulate to average all of the values in the list (using the built-in function average), and then compare the resulting value to check that it's >= 5.0.

This version of the rule will trigger for your inputs because the mathematical mean of your inputs is 6.5.

Related