Sometimes you may want to find a fact with the highest value. You can do this using not, like the example below (you could also put it in a query if you like):
rule "Highest Temperature for a Day" when Day(highest : temp) not Day(temp > highest) then...
Thanks to Mitch Christensen (mictchellch AT comcast.net) for this suggestion.
Its possible that we may introduce some "sugar" syntax to make this more declarative (open to suggestions, paste below ideas).
-
Ordered Processing of Facts
A similar approach is generally used for doing ordered processing across a set of related facts. For example, to process a set of Alert facts in the order specified by their timestamp property you could do the following:
rule "Process Alerts by time received" when $alert : Alert($oldest : timestamp) not Alert(timestamp < $oldest) then // process the Alert in some application specific way $alert.process() // remove this Alert from working memory retract($alert); end
This rule will process all Alert objects in working memory in the order in which they were generated (according to the timestamp property). As each Alert fact is processed, it is removed from working memory, and the next oldest Alert is processed next.
-Mitch Christensen
-
The Expert Systems book (Expert Systems: Principles and Programming, 4th ed., Giarratano & Riley) suggests that whilst the pattern Mitch
provided is the simplest it is not the most efficient. For N days
there would be N-squared comparisons will need to be made to find the
partial match. So if you have a large data set you may want to use the
following which should cut the partial matches down to 2N. I ran this
with 1000 days and got 2000 milliseconds for the simple rule and 234
milliseconds for the following rule. For 2000 days it was 8110 ms
versus 375 milliseconds.
TryDay and Highest are just marker classes that have one property - day. rule "Try day" when $d : Day($temp : temp) then assert(new TryDay($d)); end rule "Highest unknown" when $attempt : TryDay($d : day) not (Highest()) then assert (new Highest($d)); end rule "Highest lower" when $highest: Highest($highDay : day) $attempt : TryDay($day : day -> ($day.getTemp() > $highDay.getTemp())) then $highest.setDay($day); modify($highest); end rule "Highest higher" when Highest($highest: day) $attempt : TryDay($day : day -> ($day.getTemp() <= $highest.getTemp())) then retract($attempt); end rule "Print highest" salience -10 when Highest($day : day) then System.out.println("Highest day is " + $day); end
Steven Williams
-
Re: Highest Value Fact
My first thought at a solution was to compare each val to some temporary holder, and set the holder to the val if the val is greater. That turns out to be TERRIBLE.
Mitch's solution has the advantage of ease of understanding. Important if your rule definer is closer to a Biz Analyst than an Engineer.
Steven's obviously kicks butt in performance.
Mike Panihill
-
More on 'Finding Max'.
The problem of 'finding max' is often not restricted to a single field or attribute of the objects under consideration.
And while we are at it, let us abstract it to 'find best' or 'find optimal' pattern, which should be very applicable to many real-world situations. Best investment opportunity, best network node, best insurance policy...
Such problems have two characteristics that make a rules-based approached tempting. One, the 'goodness' criteria are subject to frequent change, and rules allow us to redefine that w/out extending our code base (e.g., the 'best new home for someone with children may not be the best for someone without kids). Two, 'goodness' can be complicated affair, often hard to capture in English, let alone in a procedural programming language.
The solution above inspired by Giarratano & Riley, while being much more efficient, will grow very rapidly once more attributes are added.
Imagine if our task was not just to find the highest temp for a day, but, assuming there can be duplicate temps, the highest temp with the highest dewPoint?
Consider
public class HeatRecord { int temp; int dewPoint; . . }
A simple solution could be:
rule "Filter" // examine each, and remove if exceeded by another salience 100 when HR1 : HeatRecord($t1:temp, $d1:dewPoint ) HeatRecord( temp > $t1 ) || HeatRecord( temp == $t1, dewPoint > $d1 ) then retract( HR1 ); end rule "Report" // report on what is left salience 50 when HR1 : HeatRecord( ) then System.out.println("The Most Sweltering Conditions were: " + HR1.getTemp() +" "+ HR1.getDewPoint() ); end
If we were to try the Giarratano & Riley approach, how big would it be? How maintainable?
Thoughts?
Mike Panihill
Comments