累加总计两次

accumulate with total counted two times

我在 Java 中有三个 类:Product、Sale 和 ProductType。 用例是按类型统计所有产品的销量。

示例:

在我的数据源(Optaplanner解决方案)中,我有很多A类和B类产品。 我用 accumulate 函数在 drools 中写了一个约束,returns 按类型正确的总计但计算了两次(每个产品实例)。

这里是 drools 约束:

    rule "products sales"
        when
           $product : Product( )
           $total : Integer() from
                 accumulate ( Product($sales: sales, productType == $product.productType) ,
                 init( int total =0;),
                 action(total+=($sales.getPrice());),
                 result( new Integer (total)))

        then
             System.out.println(
                  " Product : "+ $product.getName() +
                  " \nProduct Type : " + $productType +
                  " \nTotal sales : " + $total +
                  " \nPenalty : " + $product.getSale().totalSellsOfProductPenalty($total)
                             );
           scoreHolder.penalize(kcontext, $product.getSale().totalSellsOfProductPenalty($total));
    end

结果:

 Product : MyPRODUCT_A 
Product Type : A 
Total sales : 120 
Penalty : -60
 Product : MyPRODUCT_A 
Product Type : A 
Total sales : 120 
Penalty : -60
 Subject : MyPRODUCT_B 
Product Type : B
Total sales : 1200
Penalty : -600

=> 惩罚:-720

想要的结果:

 Product : MyPRODUCT_A 
Product Type : A 
Total sales : 120 
Penalty : -60
 Subject : MyPRODUCT_B 
Product Type : B
Total sales : 1200
Penalty : -600

=> 惩罚:-660

我该如何解决这个问题?

这里的问题不在于你的规则 -- 它非常好,你已经正确地使用了 'accumulate' 函数(很多人都有问题,包括我自己),它会按照你的要求去做到.

问题出在您传递给规则的数据上。基本上,按照您的规则构建方式,每个产品 会触发一次 。所以基本上你看到的是工作记忆中存在两个 MyPRODUCT_A 和一个 MyPRODUCT_B 的情况。该规则针对每个项目触发一次,因此您获得 MyPRODUCT_A 的两个输出和 MyPRODUCT_B.

的一个输出

有几种方法可以解决这个问题,其中最简单的方法是在工作内存中放置一个“标志”,以表示您已经触发了给定产品的规则。另一种解决方案可能是从工作记忆中收回所有该产品。还有其他方法可以解决这个问题,但这些是我能想到的最简单的方法。

(请注意,您 没有 这里有循环情况。您的规则没有循环;它只是比您想要的更多。解决方案如 no-loop对你没有好处。)


解决方案 1:工作记忆中的标志

这通常是最简单的解决方案。基本上,每次触发给定产品的规则时,都会在工作记忆中添加一个标志。然后下次规则触发时,它会在触发规则之前检查当前产品是否没有标志。

在这个例子中,我假设你想保持产品名称的唯一性,所以我将把产品名称插入到工作内存中。您应该根据您的实际需求、用例和模型进行调整。

rule "products sales"
when
  // Get the name of the current product ($name)
  $product: Product( $name: name )

  // Check that there is no flag for $name
  not( String(this == $name) )

  // Accumulate as in original
  $total: Integer() from
          accumulate ( Product($sales: sales, productType == $product.productType) ,
            init( int total =0;),
            action(total+=($sales.getPrice());),
            result( new Integer (total)))
then
  System.out.println(
    " Product : "+ $product.getName() +
    " \nProduct Type : " + $productType +
    " \nTotal sales : " + $total +
    " \nPenalty : " + $product.getSale().totalSellsOfProductPenalty($total)
  );
  scoreHolder.penalize(kcontext, $product.getSale().totalSellsOfProductPenalty($total));

  // Insert the flag
  insert($name);
end

基本上每个商品触发accumulate规则后,它的名字都会被录入working memory。如果产品名称在触发规则时在工作记忆中,则不会触发。这意味着 触发规则的每个产品都必须有一个唯一的名称;忽略重复项。

insert 操作将一条新信息添加到工作记忆中,但不是 re-trigger 以前的运行 规则。因此,如果之前触发了一条规则,则它已经结束并完成了。然而,随后的比赛现在将意识到这一新事实。

有一个非常相似的操作叫做update。此其他操作将重新触发 所有 规则和 re-evaluate 所有条件。一旦您开始调用更新,您就会开始 运行 解决与跨多个执行的循环规则相关的潜在问题。 99% 的可能性您不想在这里这样做。

这种方法的缺点通常与您拥有“标志”或其他类型的信号量的大多数情况相同——您处理的项目数量可能会失控。如果您同时评估少量产品,则开销非常低。但是想象一下,您有数百万个产品 运行 同时通过您的系统——您可能在工作内存中拥有数量惊人的字符串。这也可能导致您的应用程序和硬件出现资源问题。 (有一个关键点,您应该开始驻留字符串,这超出了本答案的范围。)

但对于一个简单的解决方案,这个解决方案“快速而肮脏”。


解决方案 2:收回项目

此解决方案仅在您不需要工作内存中的产品用于其他任何用途时才有效。也就是说,一旦 MyPRODUCT_A 触发了您的规则,您就不再需要 any MyPRODUCT_A 了。在这种情况下,我们可以在触发规则后立即从工作内存中删除所有 MyPRODUCT_A。

在示例规则中,我将再次收集具有相同名称值的所有产品,但您可以根据您的用例和模型根据需要进行更新。

rule "products sales"
when
  // Get the name of the current product ($name)
  $product: Product( $name: name )

  // Accumulate as in original
  $total: Integer() from
          accumulate ( Product($sales: sales, productType == $product.productType) ,
            init( int total =0;),
            action(total+=($sales.getPrice());),
            result( new Integer (total)))

  // Collect all of the products with the same name
  $duplicates: List() from collect( Product( name == $name ) )
then
  System.out.println(
    " Product : "+ $product.getName() +
    " \nProduct Type : " + $productType +
    " \nTotal sales : " + $total +
    " \nPenalty : " + $product.getSale().totalSellsOfProductPenalty($total)
  );
  scoreHolder.penalize(kcontext, $product.getSale().totalSellsOfProductPenalty($total));

  // Remove all of the products with the same name
  for (Product product : $duplicates) {
    retract(product);
  }
end

(请注意,retractdelete 做同样的事情。他们目前正在推动采用 'delete' 作为 'insert' 的对立面,但我们老流口水来自 back-in-the-day 的用户喜欢 'retract'。)

所以基本上你会看到的是 MyPRODUCT_A 的第一个实例符合规则,记录,然后它将从工作内存中删除 MyPRODUCT_A 的所有其他实例(和它自己)。然后 MyPRODUCT_B 将命中规则,并执行相同的操作。最终将没有产品留在工作记忆中。

显然,如果您仍然需要对 Product 做其他事情,这不是一个可行的解决方案,因为如果 Products 从内存中删除,其他规则将无法触发。但是如果你有一个非常专业的数据集,只做这件事,你可以走这条路。


正如我所说,这个问题有很多可能的解决方案。您的规则 as-is 很好。每个产品都正确触发。如何让它只针对一部分产品触发是您面临的问题,但具体如何解决取决于您和您的要求。