Search code examples
droolsboolean-logicrules

Finding the best 3 elements in a collection in Drools


Let's say I have some numbers in the working memory, and in a rule I need to check that the 3 higher numbers have a property (that they are all odd for example), how would you write that in pure Drools?

I could collect all the numbers in a list and then have a Java function that filters it taking just the best 3 (like order the list and take the last 3) and then check the property on those 3 with Drools, but I wonder if this can be done in pure Drools maybe with an accumulate?

I have tried to think for a bit but couldn't find a solution.


Solution

  • As my hint in the question, this can be achieved more elegantly with a custom accumulate function:

    rule "odd"
        when
            accumulate($l : Integer(), $r : reverseSort($l); $r.size > 2, ($r[0] % 2) == 1, ($r[1] % 2) == 1, ($r[2] % 2) == 1)
        then
            System.out.println("Best 3 are odd numbers: " + $r);
    end
    

    and the accumulate function:

    package com.test;
    
    import java.io.*;
    import java.util.*;
    
    import org.kie.api.runtime.rule.AccumulateFunction;
    
    public class ReverseSortAccumulator implements AccumulateFunction {
        private static class accumulatorData implements Serializable {   
            private static final long serialVersionUID = 1L;
    
            private List<Object> elements = new ArrayList<>();
    
            private List<Object> getElements() {
                return elements;
            }
            public void init() {
                getElements().clear();
            }
    
            public void add(Object element) {
                getElements().add(element);
            }
    
            public void remove(Object element) {
                getElements().remove(element);
            }
    
            public List<Object> reverseSort() {
                return getElements().stream()
                        .sorted(Collections.reverseOrder())
                        .collect(Collectors.toList());
            }
        }
    
        @Override
        public Serializable createContext() {
            return new accumulatorData();
        }
    
        @Override
        public void init(Serializable context) {
            getContextData(context).init();
        }
    
        @Override
        public void accumulate(Serializable context, Object input) {
            getContextData(context).add(input);
        }
    
        @Override
        public void reverse(Serializable context, Object input) {
            getContextData(context).remove(input);
        }
    
        @Override
        public List<?> getResult(Serializable context) {
            return getContextData(context).reverseSort();
        }
    
        @Override
        public boolean supportsReverse() {
            return true;
        }
    
        @Override
        public Class<?> getResultType() {
            return List.class;
        }
    
        private accumulatorData getContextData(Serializable context) {
            return (accumulatorData) context;
        }
    
        @Override
        public void writeExternal(ObjectOutput out) {
            // no need to externalise data over sessions
        }
    
        @Override
        public void readExternal(ObjectInput in) {
            // no need for externalised data from sessions
        }
    
    }