A common question is for the combinatorial explosion you get when you have parameters of the same type in a rule. (Simon has blogged on this... add link here).
I have a solution, which I will probably add to 2.0, but should be in 3.0.
I think that default behaviour should be to not have the "useless" combinations, but it can be made a rule or set attribute.
The following code shows how this is done.
package org.drools.reteoo; import org.drools.rule.Declaration; import org.drools.spi.Condition; import org.drools.spi.ConditionException; import org.drools.spi.Tuple; /** * * @author <a href="mailto:michael.neale@gmail.com"> Michael Neale</a> * Do not allow pointless cross product combinations of the SAME TYPE. * Thanks to Felipe Piccolini for the ideas for this, and Bob McWirther. */ public class CrossProductCondition implements Condition { private Declaration[] declarations; public CrossProductCondition(Declaration[] sameTypeDeclarations) { declarations = sameTypeDeclarations; } public Declaration[] getRequiredTupleMembers() { return declarations; } public boolean isAllowed(Tuple tuple) throws ConditionException { for ( int i = 0; i < declarations.length - 1; i++ ) { Declaration left = declarations[ i ]; Declaration right = declarations[ i + 1 ]; if ( !(compareIdentities( tuple, left, right )) ) { return false; } } return true; } private boolean compareIdentities(Tuple tuple, Declaration left, Declaration right) { return System.identityHashCode( tuple.get( left ) ) < System.identityHashCode( tuple.get( right ) ); } }
And the test for it:
package org.drools.reteoo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.drools.RuleBase; import org.drools.examples.ClassObjectType; import org.drools.rule.Declaration; import junit.framework.TestCase; public class CrossProductConditionTest extends TestCase { /** * Test removing cross products. * * The idea is to reduce useless cross product combinations of parameters of the same type. * It is ineffect an optimisation to reduce the effect of an explosion of * combinations. */ public void testUniqueParameterCombinations() throws Exception { RuleBase ruleBase = new RuleBaseImpl( new Rete( ) ); WorkingMemoryImpl workingMemory = (WorkingMemoryImpl) ruleBase.newWorkingMemory( ); ClassObjectType stringType = new ClassObjectType(String.class); ClassObjectType integerType = new ClassObjectType(Integer.class); //say the rule has three params Declaration d1 = new Declaration("1", stringType, 1); Declaration d2 = new Declaration("2", stringType, 2); //And these are our values String s1 = "aaa"; String s2 = "bbb"; CrossProductCondition condition = new CrossProductCondition(new Declaration[] {d1, d2}); //check that it rejects the same param twice Map values = new HashMap(); values.put(d1.getIdentifier(), s1); values.put(d2.getIdentifier(), s1); ReteTuple tuple = new MockReteTuple(workingMemory, values); assertFalse(condition.isAllowed(tuple)); //now check that it accepts only one order of different values Map valuesA = new HashMap(); valuesA.put(d1.getIdentifier(), s1); valuesA.put(d2.getIdentifier(), s2); Map valuesB = new HashMap(); valuesB.put(d1.getIdentifier(), s2); valuesB.put(d2.getIdentifier(), s1); //and finally the test. Not making any assumptions about the order of the params. //interestingly, when I run this in Eclipse, it seems to alternate with each test run... //or at least be evenly spread. Must be some time based trick for allocation in the JVM. if (condition.isAllowed(new MockReteTuple(workingMemory, valuesA))) { assertFalse(condition.isAllowed(new MockReteTuple(workingMemory, valuesB))); } else { assertTrue(condition.isAllowed(new MockReteTuple(workingMemory, valuesB))); } } static class MockReteTuple extends ReteTuple { private Map values = new HashMap(); /** * * @param wm * @param idToValue map of IDs to values to return. */ MockReteTuple(WorkingMemoryImpl wm, Map idToValue) { super(wm); values = idToValue; } public Object get(Declaration declaration) { return values.get(declaration.getIdentifier()); } } }
And finally, how to wire it in with the builder, see the following snippet (Builder.java):
/**
* Add a <code>Rule</code> to the network.
*
* @param rule
* The rule to add.
*
* @throws RuleIntegrationException
* if an error prevents complete construction of the network for
* the <code>Rule</code>.
*/
protected void addRule(Rule rule) throws RuleIntegrationException
{
Map types = new HashMap();
for ( Iterator it = rule.getParameterDeclarations().iterator(); it.hasNext(); )
{
Declaration dec = (Declaration) it.next();
ObjectType type = dec.getObjectType();
if (types.containsKey(type)) {
ArrayList list = (ArrayList) types.get(type);
list.add(dec);
} else {
ArrayList list = new ArrayList();
list.add(dec);
types.put(type, list);
}
}
for (Iterator it = types.values().iterator(); it.hasNext(); ) {
ArrayList list = (ArrayList) it.next();
if (list.size() > 1) {
CrossProductCondition condition = new CrossProductCondition((Declaration[]) list.toArray(new Declaration[] {}));
rule.addCondition(condition);
}
}


