Rule Engine--Design of Functional Programming and and/or Operators

Continuing from the previous blog post: Rule Engine – the abstraction of rule logic in the shape of "1 & (2 | 3)" , focusing on the analysis of the design of And, Or operators

Some basics of Java functional programming

BiFunction

@FunctionalInterface
public interface BiFunction<T, U, R> {
    
    

    /**
     * 将apply函数应用到给定的参数上面
     *
     * @param t 函数的第一个参数
     * @param u 函数的第二个参数
     * @return R 函数的结果
     */
    R apply(T t, U u);

    /**
     * 返回一个组合的函数,第一次是将该函数应用到它的输入,接着是将该函数的after应用到
     * 之前的结果上。如果在任一函数评测期间抛出异常,它都会被传递给组合函数的调用者。
     * @param <V> 组合函数和after函数的输出类型
     * @param after 该函数应用将被在当前函数apply后被apply
     * @return 返回一个组合函数,第一次应用该函数,接着应用after函数
     * @throws 当after为null的时候,会抛出NullPointerException异常。
     */
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
    
    
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

sample code

public static void main(String[] args) {
    
    
   // BiFunction
    BiFunction<Integer, Integer, Integer> biFunc = (x1, x2) -> x1 + x2;
    Integer result = biFunc.apply(2, 3);
    System.out.println(result); // 5

    Function<Integer,String> function = a->"result:"+a;
    String s = biFunc.andThen(function).apply(1,2);
    System.out.println(s); // result:3
}

BinaryOperator

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    
    
    /**
     * 通过比较器Comparator来比较两个元素中较小的一个作为返回值返回。
     * @param <T> 比较器的输入参数的类型
     * @param comparator 用来比较两个值的Comparator
     * @return 通过比较器Comparator来比较两个元素中较小的一个作为返回值返回。
     * @throws 如果参数为NULL,就会抛出NullPointerException异常
     */
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
    
    
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    /**
     * 通过比较器Comparator来比较两个元素中较大的一个作为返回值返回。
     * @param <T> 比较器的输入参数的类型
     * @param comparator 用来比较两个值的Comparator
     * @return 通过比较器Comparator来比较两个元素中较小的一个作为返回值返回。
     * @throws 如果参数为NULL,就会抛出NullPointerException异常
     */
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
    
    
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

sample code

public static void main(String[] args) {
    
    
    BinaryOperator<Integer> integerBinaryOperator = (x1, x2) -> x1 - x2;
    Integer result = integerBinaryOperator.apply(3, 2);
    System.out.println(result); // 1

    Integer a = BinaryOperator.minBy(Integer::compare).apply(1,2);
    System.out.println(a); // 1

    Integer b = BinaryOperator.maxBy(Integer::compare).apply(1,2);
    System.out.println(b); // 2
}

stream reduce

常见释义
英[rɪˈdjuːs][rɪˈduːs]
v.	使还原; 减少; 缩小(尺寸、数量、价格等); (使)蒸发; 减轻体重; 节食;
[例句]Better insulation of your home will help to reduce heating bills.
增加房子的隔热性能会有助于减少供暖费用。
[其他]	第三人称单数:reduces 现在分词:reducing 过去式:reduced 过去分词:reduced

Three methods of reduce overload

// 一个参数: 主要作用 累加、累减,求取最大值、最小值。
Optional<T> reduce(BinaryOperator<T> accumulator);
 
// 两个参数: 多了一个初始值, 同一个参数的方法
T reduce(T identity, BinaryOperator<T> accumulator);
 
// 三个参数:并行流使用
<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator,
             BinaryOperator<U> combiner);

Example 1

public static void main(String[] args) {
    
    
   final List<Integer> list = Arrays.asList(1, 2, 5, 3, 4);

   // reduce累加
   BinaryOperator<Integer> binaryOperatorAdd = (x1, x2) -> x1 + x2;
   Optional<Integer> result = list.stream().reduce(binaryOperatorAdd);
   System.out.println(result); // Optional[15]

   // reduce求最大值
   Optional<Integer> resultMax = list.stream().reduce(BinaryOperator.maxBy(Integer::compare));
   System.out.println(resultMax); // Optional[5]

   // reduce带初始化值的累加
   Integer result2 = list.stream().reduce(10, binaryOperatorAdd);
   System.out.println(result2); // 25

}

Example 2:

public static void main(String[] args) {
    
    
    List<Long> list = new ArrayList<>();
    for(long i = 1L;i<20_000_000L;i++){
    
    
        list.add(i);
    }
    BinaryOperator<Long> binaryOperatorAdd = (x1, x2) -> x1 + x2;
    long sta = System.currentTimeMillis();
    Long result = list.stream().reduce(0L, binaryOperatorAdd, binaryOperatorAdd);
    long cost = System.currentTimeMillis() - sta;
    System.out.println(result + " cost:" + cost);


    long sta2 = System.currentTimeMillis();
    Long result2 = list.parallelStream().reduce(0L, binaryOperatorAdd, binaryOperatorAdd);
    long cost2 = System.currentTimeMillis() - sta2;
    System.out.println(result2 + " cost:" + cost2);

}

The time-consuming gap of some operations is as follows
insert image description here

And, Or operator

insert image description here

  • AbstractAndOr
public abstract class AbstractAndOr extends AbstractShortcutableEvaluable<Evaluable<EvalResult>, EvalResult> implements
        Operator<Evaluable<EvalResult>, EvalResult> {
    
    

    @Override
    protected BinaryOperator<EvalResult> getCombiner(EvalResult s) {
    
    
        return (r1, r2) -> {
    
    
            if (r1 == s || r2 == s) {
    
    
                return s;
            } else if (r1 == EvalResult.Unknown || r2 == EvalResult.Unknown) {
    
    
                return EvalResult.Unknown;
            } else if (r1 == EvalResult.Exception || r2 == EvalResult.Exception) {
    
    
                return EvalResult.Exception;
            } else {
    
    
                return initial();
            }
        };
    }
}
  • AbstractShortcutableEvaluable
public abstract class AbstractShortcutableEvaluable<E extends Evaluable<T>, T> extends
        AbstractOperandsBasedEvaluable<E, T> {
    
    

    /**
     * 定义两操作数求值结果的结合逻辑
     *
     * @param shortcut
     * @return
     */
    protected abstract BinaryOperator<T> getCombiner(T shortcut);

    /**
     * 定义短路值
     *
     * @return
     */
    public abstract T shortcut();

    /**
     * 定义初始值
     *
     * @return
     */
    public abstract T initial();


    @Override
    public T eval(EngineExecutionContext context) {
    
    
        return getOperands().stream().reduce(initial(), getAccumulator(context, shortcut()),
                getCombiner(shortcut()));
    }

    private BiFunction<T, Evaluable<T>, T> getAccumulator(EngineExecutionContext context, T shortcut) {
    
    
        return (r, op) -> r.equals(shortcut) ? r : getCombiner(shortcut).apply(r,
                op.eval(context));
    }
}

The Combiner of the and logic is as follows:

  • and operator
public class And extends AbstractAndOr {
    
    

    @Override
    public EvalResult initial() {
    
    
        return EvalResult.True;
    }

    @Override
    public EvalResult shortcut() {
    
    
        return EvalResult.False;
    }

    @Override
    public String getOperator() {
    
    
        return Operator.AND;
    }

    @Override
    public void accept(EvaluableVisitor visitor) {
    
    
        visitor.visit(this);
    }
}

The shortcut of and is false, the initial value is true , then the combiner logic

BinaryOperator<EvalResult> getCombiner(EvalResult s) {
    
    
	// s 为 EvalResult.False
  return (r1, r2) -> {
    
    
        if (r1 == s || r2 == s) {
    
    
            return s;
        } else if (r1 == EvalResult.Unknown || r2 == EvalResult.Unknown) {
    
    
            return EvalResult.Unknown;
        } else if (r1 == EvalResult.Exception || r2 == EvalResult.Exception) {
    
    
            return EvalResult.Exception;
        } else {
    
    
            return EvalResult.True;
        }
    };
}

That is, as long as there is one False, it is false, and the execution of unknown and abnormal conditions is handled separately, otherwise it returns true

The Combiner of or logic is as follows:

  • or operator
public class Or  extends AbstractAndOr {
    
    

    @Override
    public EvalResult initial() {
    
    
        return EvalResult.False;
    }

    @Override
    public EvalResult shortcut() {
    
    
        return EvalResult.True;
    }

    @Override
    public String getOperator() {
    
    
        return Operator.OR;
    }

    @Override
    public void accept(EvaluableVisitor visitor) {
    
    
        visitor.visit(this);
    }
}

The shortcut of or is true, the initial value is false , then the combiner logic

BinaryOperator<EvalResult> getCombiner(EvalResult s) {
    
    
	// s 为 EvalResult.True
  return (r1, r2) -> {
    
    
        if (r1 == s || r2 == s) {
    
    
            return s;
        } else if (r1 == EvalResult.Unknown || r2 == EvalResult.Unknown) {
    
    
            return EvalResult.Unknown;
        } else if (r1 == EvalResult.Exception || r2 == EvalResult.Exception) {
    
    
            return EvalResult.Exception;
        } else {
    
    
            return EvalResult.False;
        }
    };
}

That is, as long as there is one True, it is true, and the unknown and abnormal conditions are handled separately, otherwise false is returned

and, or execution

The above explained and, or the Combiner is correct, let's look at its execution

 @Override
public T eval(EngineExecutionContext context) {
    
    
   return getOperands().stream().reduce(initial(), getAccumulator(context, shortcut()),
           getCombiner(shortcut()));
}

private BiFunction<T, Evaluable<T>, T> getAccumulator(EngineExecutionContext context, T shortcut) {
    
    
   return (r, op) -> r.equals(shortcut) ? r : getCombiner(shortcut).apply(r, op.eval(context));
}

return (r, op) -> r.equals(shortcut) ? r : getCombiner(shortcut).apply(r, op.eval(context));For and, if the first result of the ternary operator is false, then the result will be returned directly (because the sentence r.equals(shortcut) will satisfy), that is, one of the left and right quantities is false, and there is no need for and, just directly false

return (r, op) -> r.equals(shortcut) ? r : getCombiner(shortcut).apply(r, op.eval(context));For or, if the first result of the ternary operator is true, then the result will be returned directly (because the sentence r.equals(shortcut) will satisfy), that is, one of the left and right sides is true, and there is no need for or, just directly true

Otherwise, the logic of the combiner needs to be executed

So the and, or operator symbols are implemented as follows,

getOperands().stream().reduce(
参数1:	EvalResult.True,
参数2: getAccumulator 同	BinaryOperator<EvalResult> getCombiner, 有短路逻辑
参数3:	BinaryOperator<EvalResult> getCombiner
```

如下截图,反映了其中一次and操作符的执行
![在这里插入图片描述](https://img-blog.csdnimg.cn/70f8b3aaa0174366888b11f9585cf597.png)

Guess you like

Origin blog.csdn.net/qq_26437925/article/details/131348032