Java Optaplanner约束流

Java Optaplanner约束流,java,optaplanner,Java,Optaplanner,我正在与“更高级别”的约束流(bi、tri..)作斗争。Optaplanner用户指南中显示了最简单的案例,我理解它们。同样,通过这些例子,我能够对一个双CONSTRAINTSTREAM进行分组求和。 现在我遇到了一个棘手的问题。有人知道更好地理解ConstraintStreams的好信息源吗 我的案例:问题结构与运输问题非常相似。我有节点和边,想计算边的数量 我的主要问题是: class Node{ UUID id; int itemsInInventory; ... }

我正在与“更高级别”的约束流(bi、tri..)作斗争。Optaplanner用户指南中显示了最简单的案例,我理解它们。同样,通过这些例子,我能够对一个双CONSTRAINTSTREAM进行分组求和。 现在我遇到了一个棘手的问题。有人知道更好地理解ConstraintStreams的好信息源吗

我的案例:问题结构与运输问题非常相似。我有节点和边,想计算边的数量

我的主要问题是:

class Node{
   UUID id;
   int itemsInInventory;
   ...
}
以及规划实体:

@PlanningEntity
class Edge{
   Node from;
   Node to; 
   @PlanningVariable
   int itemsTransported;

   getFromId(){return from.getId()}
   getToId(){return to.getId()}

}
我想将边连接两次到节点中,然后groupBy它们确实有一个类似于
(Node,Sum(to),Sum(from))的对象。

约束的当前代码为:

public Constraint minimizeShortage(ConstraintFactory constraintFactory) {
    return constraintFactory.fromUnfiltered(Node.class)
            .join(Edge.class,
                    equal(Node::getId, Edge::getFromId))
            .join(Edge.class,
                    equal((node, edge) -> node.getId(), Edge::getToId))
            groupBy(...)
}

对于BiConstraintStream(仅使用fromNode或toNode),groupBy将是
。groupBy((node,edge)->node,sum((node,edge)->edge.getItemsTransported())
,但我在第二次连接之后没有得到TriConstraintStream。

我找到了解决问题的方法。解决方案是使用下面的语法,尤其是不要在一个groupBy中进行总结。我必须将求和分为两个GroupBy,并按以下顺序计算:Join1-groupBy1-join2-groupBy2

public Constraint minimizeShortage(ConstraintFactory constraintFactory) {
    return     constraintFactory.fromUnfiltered(Node.class)
            .join(Edge.class,
                    equal(Node::getId, Edge::getFromId))
            .groupBy((node, edgeFrom) -> node,
                    sum((node, tsaO) -> edgeFrom.transportedItems()))
            .join(Edge.class,
                    equal((node, edge) -> node.getId(), Edge::getToId))
            .groupBy((node, edgeFrom, edgeTo) -> node,
                    (node, edgeFrom, edgeTo) -> qtO,
                    sum((node, qtO, edgeTo) -> edgeTo.transportedItems()))
            ...
}

我找到了解决问题的办法。解决方案是使用下面的语法,尤其是不要在一个groupBy中进行总结。我必须将求和分为两个GroupBy,并按以下顺序计算:Join1-groupBy1-join2-groupBy2

public Constraint minimizeShortage(ConstraintFactory constraintFactory) {
    return     constraintFactory.fromUnfiltered(Node.class)
            .join(Edge.class,
                    equal(Node::getId, Edge::getFromId))
            .groupBy((node, edgeFrom) -> node,
                    sum((node, tsaO) -> edgeFrom.transportedItems()))
            .join(Edge.class,
                    equal((node, edge) -> node.getId(), Edge::getToId))
            .groupBy((node, edgeFrom, edgeTo) -> node,
                    (node, edgeFrom, edgeTo) -> qtO,
                    sum((node, qtO, edgeTo) -> edgeTo.transportedItems()))
            ...
}

我将以前面答案中的代码作为起点:

public Constraint minimizeShortage(ConstraintFactory constraintFactory) {
    return constraintFactory.fromUnfiltered(Node.class)
        .join(Edge.class,
                equal(Node::getId, Edge::getFromId))
        .groupBy((node, edgeFrom) -> node,
                sum((node, tsaO) -> edgeFrom.transportedItems()))
        .join(Edge.class,
                equal((node, edge) -> node.getId(), Edge::getToId))
        .groupBy((node, edgeFrom, edgeTo) -> node,
                (node, edgeFrom, edgeTo) -> qtO,
                sum((node, qtO, edgeTo) -> edgeTo.transportedItems()))
        ...
}
这段代码不太可能执行得很好,因为
groupBy()
非常昂贵,将它们链接起来会使问题逐渐恶化。让我们看看是否可以只使用一个
groupBy()

让我们再讨论一下。这个特殊的
groupBy()
重载由两部分组成:键映射和收集器

键映射将三元组拆分为组-在这种情况下(
(node,edgeFrom,edgeTo)->node
),具有相同
节点的所有三元组将归入同一组

然后,每个采集器生成对每个这样的三元组应用给定操作(
sum
)的结果。我们在这里使用两个收集器,一个用于汇总在
edgeFrom
中传输的项目,另一个用于
edgeTo
中传输的项目。这两个收集器仅适用于共享相同
节点的边,正如前面提到的密钥映射所保证的那样


作为旁注,在开发类似这样的非平凡约束时,我绝对建议使用对一些典型结果进行建模,并查看约束的行为是否符合预期。

我将以前面答案中的代码作为我的出发点:

public Constraint minimizeShortage(ConstraintFactory constraintFactory) {
    return constraintFactory.fromUnfiltered(Node.class)
        .join(Edge.class,
                equal(Node::getId, Edge::getFromId))
        .groupBy((node, edgeFrom) -> node,
                sum((node, tsaO) -> edgeFrom.transportedItems()))
        .join(Edge.class,
                equal((node, edge) -> node.getId(), Edge::getToId))
        .groupBy((node, edgeFrom, edgeTo) -> node,
                (node, edgeFrom, edgeTo) -> qtO,
                sum((node, qtO, edgeTo) -> edgeTo.transportedItems()))
        ...
}
这段代码不太可能执行得很好,因为
groupBy()
非常昂贵,将它们链接起来会使问题逐渐恶化。让我们看看是否可以只使用一个
groupBy()

让我们再讨论一下。这个特殊的
groupBy()
重载由两部分组成:键映射和收集器

键映射将三元组拆分为组-在这种情况下(
(node,edgeFrom,edgeTo)->node
),具有相同
节点的所有三元组将归入同一组

然后,每个采集器生成对每个这样的三元组应用给定操作(
sum
)的结果。我们在这里使用两个收集器,一个用于汇总在
edgeFrom
中传输的项目,另一个用于
edgeTo
中传输的项目。这两个收集器仅适用于共享相同
节点的边,正如前面提到的密钥映射所保证的那样


作为旁注,在开发像这样的非平凡约束时,我绝对建议使用来模拟一些典型的结果,并查看约束的行为是否符合预期。

即使这可以实现您希望的功能(我衷心建议您使用constraint Verifier()来验证这一点),它的性能也不会很好。单个约束中的多个groupby()是消除分数计算计数的好方法。我将很快提供一个不同的答案,尽管这可能会实现您希望它实现的功能(我衷心建议您使用Constraint Verifier()验证这一点),但它的性能不会很好。单个约束中的多个groupby()是消除分数计算计数的好方法。我很快会提供一个不同的答案。谢谢你的解释。你的建议正是我最初试图建立的。但是您的解决方案不起作用,并导致语法错误。无法解析“对象”中的方法“transportedItems()”。编译器不明白,edgeTo是从类EdgeI中发现的另一个故障,内部连接也是一个问题。之后第一次加入我会得到一个列表,如果电台是供应商(“nodeFrom”)。在节点中。我踢所有没有边且在nodeTo属性中有边的节点。在第二次加入之后,我得到了一个所有站点的列表,这些站点是供应商和另一个节点的需求者。所以我踢出了所有不是两者都踢的节点。我想做的是得到一个类似“Node,EdgeWhereNodeFrom,EdgeWhereNodeTo”的结果,其中Edge是所有可能的边的列表,EdgeWhereNodeFrom和EdgeWhereNodeTo是节点所在的边。这些流畅的API在编译器中有时会很棘手。如果您遇到类型错误,这通常是因为流中的其他内容出错,并且编译器无法从整体上解析fluent调用链。据我所知,我的代码示例没有任何问题,因此我猜测在写“…”的部分中有一些错误,我们在这里看不到。至于您的第二条与连接相关的评论,我建议我们