Drools 累计总计数两次

Drools 累计总计数两次,drools,optaplanner,Drools,Optaplanner,我在Java中有三个类:Product、Sale和ProductType。 用例是按类型划分的所有产品的销售计数 例如: A类产品的总销售额:500欧元 B类产品的总销售额:500欧元 在我的数据源(Optaplanner解决方案)中,我有许多A型和B型产品。 我在drools中使用累积函数编写了一个约束,该函数按类型返回正确的总数,但计数两次(每个产品实例) 以下是drools约束: rule "products sales" when

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

例如:

  • A类产品的总销售额:500欧元
  • B类产品的总销售额:500欧元
在我的数据源(Optaplanner解决方案)中,我有许多A型和B型产品。 我在drools中使用累积函数编写了一个约束,该函数按类型返回正确的总数,但计数两次(每个产品实例)

以下是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


我该如何解决这个问题呢?

这里的问题不在于你的规则——它很好,你已经正确地使用了“累积”功能(包括我在内,很多人都有这个问题),它会按照你的要求执行

问题在于传递到规则中的数据。基本上,按照规则的结构,每个产品将触发一次。所以基本上你看到的是一种情况,在工作记忆中有两个MyPRODUCT_a和一个MyPRODUCT_B。该规则为每个项目触发一次,因此您将获得MyPRODUCT_A的两个输出和MyPRODUCT_B的一个输出

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

(请注意,这里没有循环情况。您的规则没有循环;它只是触发了超出您希望的次数。像
无循环
这样的解决方案对您没有好处。)


解决方案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
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
基本上,在每个产品触发累加规则后,其名称会被输入工作记忆。如果在触发规则时产品名称在工作内存中,则不会触发。这意味着每个触发规则的产品必须有一个唯一的名称;重复项将被忽略

insert
操作将一条新信息添加到工作内存中,但不会重新触发以前运行的规则。因此,如果以前触发了一条规则,那么它就已经结束了。然而,随后的比赛现在将知道新的事实

有一个非常类似的操作叫做
update
。此其他操作将重新引用所有规则,并重新评估所有条件。一旦开始调用update,您就开始遇到跨多个执行循环规则的潜在问题。99%的可能性是你不想在这里这样做

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

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


解决方案2:收回项目

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

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

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
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
(请注意,
retract
delete
也做了同样的事情。他们目前正在推动采用“delete”作为“insert”的反面,但美国老用户对“retract”很感兴趣。)

因此,基本上您将看到MyPRODUCT_A的第一个实例符合规则log,然后它将从工作内存中删除MyPRODUCT_A的所有其他实例(及其本身)。然后MyPRODUCT_B将符合规则,并执行相同的操作。最终,工作记忆中不会留下任何产品

显然,如果您仍然需要对产品执行其他操作,这不是一个可行的解决方案,因为如果产品从内存中删除,这些其他规则将无法启动。但是如果你有一个非常专门的数据集,它只做一件事,你可以走这条路



正如我所说,这个问题有很多可能的解决办法。你的规则是好的。每种产品都能正确点火。如何让它只为一部分产品启动是您面临的问题,但您如何准确地解决它取决于您和您的要求。

这里的问题不在于您的规则,它非常好,您正确地使用了“累积”功能(包括我在内的许多人都有问题),它会按照你的要求去做

问题在于传递到规则中的数据。基本上,按照规则的结构,每个产品将触发一次。所以基本上你看到的情况是,你有两个MyPRODUCT_a和一个MyPRODUCT_B