3.1ConstraintStream约束编写

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

内容概要

到此为止,我们已经学习了OptaPlanner很多的概念,从今天开始我们来学习编写约束规则评分的两种常用的方式,其它的方式在扩展和易用性上面要差的很多,所以我们只学习常用的两种。今天我们来开始学习ConstraintStream方式约束编写。

Constraint Stream

Constraint Stream是一种纯Java的增量分数计算的函数式编程方法,易于阅读、编写和调试。如果曾经使用过Java 8 Streams或SQL,是很容易上手的。

ConstraintStreams/ConstraintProvider API是一个正在开发中的项目。它可以使用,但它有许多API的空白。因此,它还不够丰富,无法处理复杂的约束。约束的内容可能无法正常执行,不过通常对我们来说已经够用了。

简介

使用Java 8的Streams API,我们可以实现一个使用函数式方法的简易分数计算:

    private int doNotAssignAnn() {
        int softScore = 0;
        schedule.getShiftList().stream()
                .filter(Shift::isEmployeeAnn)
                .forEach(shift -> {
                    softScore -= 1;
                });
        return softScore;
    }
复制代码

然而,这种方法的扩展性很差,因为它没有做增量计算。当单个Shift的**规划变量(PlanningVariable)**发生变化时,为了重新计算得分,普通的Streams API必须从头开始执行整个流。

而ConstraintStreams API可以用纯Java编写类似的代码,同时获得增量分数计算的性能优势。这是一个使用Constraint Streams API的相同代码的例子:

    private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .filter(Shift::isEmployeeAnn)
                .penalize("Don't assign Ann", HardSoftScore.ONE_SOFT);
    }
复制代码

这个约束流遍历了**问题事实(ProblemFact)中的所有Shift类实例和规划问题(PlanningProblem)中的规划实体(PlanningEntity)**。它找到每一个分配给人员Ann的Shift,对于每一个这样的实例(也称为匹配),它在总分上增加一个1的软惩罚。下图说明了在一个有4个不同班次的问题上的这个过程:

constraintStreamIntroduction_edit.png

如果在求解过程中任何PlanningEntity实例发生了变化,Constraint Stream约束流会自动检测到这种变化,并且只重新计算受变化影响的问题的最小必要部分。下图说明了这种递增的分数计算:

constraintStreamIncrementalCalculation_edit.png

从此图可以看出,只需要计算2个即可,也许会有人问3feb从Ann变更成了Beth,但是我们的增量计算里并没有加分的计算。则是因为我们再调用Score计算分值时,会有个Listener监听记录此次匹配信息及分值的信息,若规划变量发生了变化,不在跟约束条件所匹配,则会回溯之前的分值。

总结

这一篇章大家要理解ConstraintStream的增量计算的逻辑,这对我们编写约束是非常关键的。

结束语

下一篇章我们来学习如何编写一个ConstraintStream,以及如何测试一个约束。

创作不易,禁止未授权的转载。如果我的文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧💕💕💕💕💕💕