初学OptaPlanner-06- Stream流式处理约束

1,240 阅读3分钟

原文学习链接:
docs.optaplanner.org/7.45.0.Fina…

stream流说白了,和SQL的基本操作类似,熟练掌握SQL,流式处理数据基本意思相同。

约束流得分计算

6.1 约束流可以实现增量流计算

普通的SQL或者Java的Stream流不支持增量计算,流中的一个元素发生改变需要全体重新计算一遍;
在计算过程中constraint stream支持自动检测发生的变化,并且可以只重新计算只发生变化的部分。

6.2 创建一个 constraint stream

public class MyConstraintProvider implements ConstraintProvider {

        @Override
        public Constraint[] defineConstraints(ConstraintFactory factory) {
            return new Constraint[] {
                    penalizeEveryShift(factory)
            };
        }
		// 在这里实现constraint stream
        private Constraint penalizeEveryShift(ConstraintFactory factory) {
            return factory.from(Shift.class)
                .penalize("Penalize a shift", HardSoftScore.ONE_SOFT);
        }

    }

6.3. Constraint stream cardinality (约束流基数)

支持四级基数:

CardinalityPrefixDefining interface
1UniUniConstraintStream<A>
2BiBiConstraintStream<A, B>
3TriTriConstraintStream<A, B, C>
4QuadQuadConstraintStream<A, B, C, D>
代码示例:
// 示例1
 private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class) // Returns Uni<Shift>.
                .join(Employee.class)    // Returns Bi<Shift, Employee>.
                .join(DayOff.class)      // Returns Tri<Shift, Employee, DayOff>.
                .join(Country.class)     // Returns Quad<Shift, Employee, DayOff, Country>.
                ...
    }
// 示例2
private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class) // Returns UniStream<Shift>.
                .join(Employee.class) // Returns BiStream<Shift, Employee>.
                .groupBy((shift, employee) -> employee) // Returns UniStream<Employee>.
                ...
    }
    

6.4 Building blocks (构建块)

简单示例:

 private Constraint penalizeInitializedShifts(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .penalize("Initialized shift", HardSoftScore.ONE_SOFT);
    }

6.4.1. Penalties and rewards (惩罚和奖励)

每个 constraint stream流都需要用a penalize() or a reward()来构建基本块。 无论是惩罚还是奖励都有以下的特点:

  • 实现ConstraintProvider接口
  • Constraint name 可以随意自定义,为了可读性而已
  • Constraint weight:约束的权重类别;支持SimpleScore.ONE, HardSoftScore.ONE_HARD and HardMediumSoftScore.of(1, 2, 3)等类别.
  • Constraint match weigher:默认是1,多次匹配多次累乘(multiplied)这个系数

constraint stream支持不同类别的约束项,API的例子如下:

  • 简单类别:penalize("Constraint name", SimpleScore.ONE)
  • 动态类别: penalize("Constraint name", SimpleScore.ONE, Shift::getHours); Shift::getHours相当于是一个动态的约束权重. (目前代码演示不通)
  • 支持配置化的简单和动态类别.

6.4.2 Filtering

同Java8的流处理:

  private Constraint penalizeShiftsOnOffDays(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .join(DayOff.class)
                .filter((shift, dayOff) -> shift.date == dayOff.date && shift.employee == dayOff.employee)
                .penalize("Shift on an off-day", SimpleScore.ONE);
    }

6.4.3. Joining内连接 (Joiner)

  • 和sql的inner join...on...相似
 private Constraint shiftOnDayOff(ConstraintFactory constraintFactory) {
        return constraintFactory.from(Shift.class)
                .join(DayOff.class,
                    Joiners.equal(Shift::getDate, DayOff::getDate),
                    Joiners.equal(Shift::getEmployee, DayOff::getEmployee))
                .penalize("Shift on an off-day",
                        HardSoftScore.ONE_HARD);
    }

Joiner 还支持以下语法:

  • equal for joining constraint matches where they equals() one another.
  • greaterThan, greaterThanOrEqual, lessThan and lessThanOrEqual for joining Comparable constraint matches per the prescribed ordering.

6.4.4. Grouping and collectors

分组:同jdk的Stream的功能 collectors:

  • 聚合作用,支持sum()、count()、countDistinct()、sumLong()等
  • 还支持ConstraintCollectors.min(…​) and ConstraintCollectors.max(…​)

6.4.5 条件传播 (Conditional propagation)

  • ifExist()
private Constraint runningComputer(ConstraintFactory constraintFactory) {
        return constraintFactory.from(CloudComputer.class)
                .ifExists(CloudProcess.class, Joiners.equal(Function.identity(), CloudProcess::getComputer))
                .penalize("runningComputer",
                        HardSoftScore.ONE_SOFT,
                        computer -> ...);
    }
  • ifNotExists() 略

6.5 测试一个约束流 (单测)

用Opta自己的测试框架来测试约束流,在求解问题之前进行检验约束

  • 构建一个简单的N皇后的约束
protected Constraint horizontalConflict(ConstraintFactory factory) {
        return factory
                .fromUniquePair(Queen.class, equal(Queen::getRowIndex))
                .penalize("Horizontal conflict", SimpleScore.ONE);
    }
  • 使用约束的 Verifier API 来创建一个单测
   private ConstraintVerifier<NQueensConstraintProvider, NQueens> constraintVerifier
            = ConstraintVerifier.build(new NQueensConstraintProvider(), NQueens.class, Queen.class);

    @Test
    public void horizontalConflictWithTwoQueens() {
        Row row1 = new Row(0);
        Column column1 = new Column(0);
        Column column2 = new Column(1);
        Queen queen1 = new Queen(0, row1, column1);
        Queen queen2 = new Queen(1, row1, column2);
        constraintVerifier.verifyThat(NQueensConstraintProvider::horizontalConflict)
                .given(queen1, queen2)
                .penalizesBy(1);
    }

6.7 约束流还支持Drools的实现方式

原文翻译: 约束流有两种风格,一种默认使用的是Drools的默认实现,另一种是名为Bavet的纯Java实现。基于Drools的实现功能更完整。这两个变量都实现了相同的ConstraintProvider API。在这两者之间切换不需要Java代码更改。
Bavet是一个实验性的实现,它关注原始速度并提供优异的性能。但是,它缺少特性,因此许多示例不受支持。

综上,个人觉得还是Stream流的方式更容易上手。 第七章全章节都在讲述Drools score calculation的一种类似脚本的实现方式,这章直接跳过,快进到第8章。

                                                                2020年11月3日10:06:48