Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
Simple enough from the sound of things, apparently the controversy is with regards to the statement attached to it of "199 out of 200 programmers couldn't do it". Well, not one to shy away from a challenge, I thought that it would be cool to see whether I could implement FizzBuzz using our current rules engine technology. So with a bit of tinkering and some hocus-pocus I am proud to present the FizzBuzz solution as done in a rules engine. Yes, it is more verbose than it should be, but while I was doing it I thought to myself - "Self, wouldn't it be cool to provide a totally useless example of flowrules as well as the standard if... then rule syntax?". So without further ado...
Step1: The 'Business Model'
Well, I needed something that the rules will run on. It had to represent a number, it had to be incrementable, and due to a constraint in the rules engine, it had to be able to do a mod operation. I came up with the following very simple construct called imagineatively 'MyInteger'.
public class MyInteger {
private int myInt = 0;
public void setValue(int aValue)
{
myInt = aValue;
}
public int getValue()
{
return myInt;
}
public void increment()
{
myInt = myInt + 1;
}
public int mod(int aValue)
{
return myInt % aValue;
}
}
I also decided to make my life a teeny bit easier and provided an increment() convenience method. So, my business object in place, I was now ready to start playing within the rules engine IDE. Your milage may vary depending on the specific rules engine you want to use (as if you will ever get a question to do this). :)
Step2: The Rules Project
Let's look at the problem statement again, shall we?
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
Mmm, four rules spring to mind
- If the number is a multiple of three, print 'Fizz'
- If the number is a multiple of five print 'Buzz'
- if the number is a multiple of three as well as a multiple of five print 'FizzBuzz'
- if none of the above is true, print the number
Now, one could argue that rule (3) is simply an ordering of the output of rules (1) and (2). I made a decision to implement it as a seperate rule to stay within the spirit of printing "FizzBuzz". Yes, pedantic... And yes Brian, mod 15 will work as well...
So, this is what the four rules look like after they were defined
First we have rule 'Fizz'. Quite simple, really. I defined a precondition that one of the other rules (FizzBuzz) should not be satisfied. What the rules engine will do is to use the preconditions, along with priorities to construct the internal RETE tree. In this specific case if 'FizzBuzz is not satisfied (i.e. the value is not divisible by 3 and also not divisible by 5) this rule may fire.
RuleName | |
Status | Active |
Effectivity | Always |
Priority | Medium |
Sub-Priority | 50 |
Description | Display Fizz if the value is divisible by 3 |
Preconditions | |
Rule 'FizzBuzz' must not be satisfied | |
If | |
(za.co.passif.MyInteger Not Equals null | |
AND za.co.passif.MyInteger.mod(3) Equals 0 ) | |
Then | |
Execute Method:: Print Message Fizz |
The second rule is 'Buzz'. Similar to 'Fizz' in nature, except for the divisible by 5 bit.
_______________________________________________________ | |
RuleName | |
Status | Active |
Effectivity | Always |
Priority | Medium |
Sub-Priority | 50 |
Description | Display Buzz if value is divisible by 5 |
Preconditions | |
Rule 'FizzBuzz' must not be satisfied | |
If | |
(za.co.passif.MyInteger Not Equals null | |
AND za.co.passif.MyInteger.mod(5) Equals 0 ) | |
Then | |
Execute Method:: Print Message Buzz |
The third rule simply prints out the value if none of the other rules have fired. Unfortunately the rules engine that is used does not have a 'good looking' print mechanism, so I had to cheat a bit and am printing a space and then the value.
_______________________________________________________ | |
RuleName | |
Status | Active |
Effectivity | Always |
Priority | Medium |
Sub-Priority | 50 |
Description | Display the value if it is not divisible by 3 as well as not divisible by 5 |
Preconditions | |
Rule 'Buzz' must not be satisfied | |
Rule 'Fizz' must not be satisfied | |
Rule 'FizzBuzz' must not be satisfied | |
If | |
za.co.passif.MyInteger Not Equals null | |
Then | |
Execute Method:: Print +za.co.passif.MyInteger.getValue |
And lastly the 'FizzBuzz' rule. You will notice there are no preconditions. It also has one other difference, namely I used the priority mechanism to ensure that this rule is executed first. It's priority is set to 100 where-as the other rules are set to a priority of 50.
_______________________________________________________ | |
RuleName | |
Status | Active |
Effectivity | Always |
Priority | Medium |
Sub-Priority | 100 |
Description | Display FizzBuzz if the value is divisible by 3 as well as divisible by 5 |
If | |
(za.co.passif.MyInteger Not Equals null | |
AND za.co.passif.MyInteger.mod(3) Equals 0 | |
AND za.co.passif.MyInteger.mod(5) Equals 0 ) | |
Then | |
Execute Method:: Print Message FizzBuzz |
Step 3: Controlling the Flow
So this is great - we have a ruleset that can evaluate a single value and determine whether based on the value itself, it should print 'Fizz', 'Buzz', 'FizzBuzz' or the actual value. However it does not satisfy the requirement to be able to iterate through a set of numbers from 1 to 100. Now how to handle this? Well, since we are already overkilling the problem by using a rules engine, let's take it a step further and throw in the use of another tool in the rules engine arsenal, namely a FlowRuleSet. The FlowRuleset will define the flow and the iteration through the collection.
In overkill fashion, I managed to introduce not only three tasks, but a decision point as well. The tasks are responsible for incrementing the value, executing the four rules in the FizzBuzz ruleset and printing a pretty end statement. The decision point is used to determine whether the end value (100) has been reached.
Conclusion
Total Overkill (and yes, this worked like a charm). An enjoyable way to play around with the rules engine that we use - and I am not sure whether I can mention the name, thus it is not used. I am keen though to go through the same exercise with something like Drools. Or maybe see whether one can do this with a decisions table.
So, Brian, the gauntlet has been thrown down - care to come up with that more complex example of FizzBuzz that we discussed?
1 comment:
Nice! As you say, a Drools comparison to (insert_name_of_secret_rules_engine_here) would be interesting.
Post a Comment