Counting

== THIS IS OLD AND NEEDS UPDATE - SEE ACCUMULATE FUNCTIONS IN THE MANUAL FOR DETAILS ==

 

Counting Facts To Use In The LHS

 

We had a requirement for our rules to calculate total number of facts meeting certain criteria as a condition for the rule firing. Since there was nothing natively available in the LHS to do an equivalent of a count() function, I want to document exactly what we implemented and try to capture others ideas.

 

As an example, let's say we have Student with an associated State, and a rule that insures we only enroll new students from out of state after we have at least 100 enrolled from Alaska (I am not from Alaska nor is this an enrollment system for a school, btw ;-).

 

Ideally, I was hoping to do something like this:

when
     state : State(abbrev == "AK")

     # THIS WILL NOT WORK
     eval( Student(state == state, status == "enrolled") < 100 )

     student : Student( s:state -> (s.abbrev != "AK"), status == "new")
then
     quotaOfStateStudentsNotMet(student);

 

 

However, I realized that one fact at a time is evaluated, and I wrongly assumed that eval( Student() < 100 ) would produce some kind of total.

 

So what WILL produce a count? A query will indirectly allow you can count facts by getting the size() of the returned Query Results.

 

So in the DRL file under the rule, I created a query with the following:

query "Count Total In-State Students Enrolled"
     student : Student (s:state ->(s.abbrev == "AK"), status == "enrolled")
end

 

 

SIDE NOTE: breaking out the predicate in the above will fail the query. You cannot do state:State(abbrev=="AK") first. It is expecting only a single line, from what I found.

 

Now, you cannot evaluate a query directly from within the LHS. But you CAN call a function through an eval(). So what I ended up doing was creating a function that would support any query and return the resulting size of that query:

 

 

From the same DRL file

import org.drools.QueryResults;
...
function int countFacts(String queryString) {
     QueryResults queryResults = EnrollmentRulesEngine.getQueryResults(queryString);
     if (queryResults != null) {
          System.out.println("Total FACTS found: " + queryResults.size());
          return queryResults.size();
     }
     return 0;
}

 

 

This also assumes a static method in your engine is available to execute a query against the current Memory System. So in the code you'll need something like this:

     public static QueryResults getQueryResults(String query) {
          return workingMemory.getQueryResults(query);
     }

 

 

Putting all this together gives you the ability to write the LHS conditional to count facts, using a query and delegate function, as follows:

rule "Do not enroll out-of-state students until in-state quota is reached"
     when 
          state : State(abbrev != "AK")
          student : Student( state == state, status == "new")
          eval( countFacts     ("Count Total In-State Students Enrolled")     < 100 )
     then
          System.out.println("Cannot enroll student: " + student.getName() +" until 100 Alaskan students are enrolled");
end

 

Referenced by: