累计两次计算

问题描述

我在Java中有三个类:Product,Sale和ProductType。 用例是按类型列出的所有产品的销售额。

示例:

  • A类产品的总销售额:500欧元
  • B类产品的总销售额:500欧元

在我的数据源(Optaplanner解决方案)中,我有许多A型和B型产品。 我用累积功能在流口水中写了一个约束,该约束按类型返回正确的总数,但计数了两次(每个产品实例)。

这是流口水的约束:

    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的一个输出。

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

(请注意,这里没有 循环的情况。您的规则不是循环的;它触发的次数超出了您的预期。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

基本上,每个产品触发累积规则后,其名称都会输入到工作存储器中。如果产品名称在触发规则时处于工作记忆中,则不会触发。这意味着执行触发规则的每个产品都必须具有唯一的名称;重复项将被忽略。

insert操作向工作内存中添加了一条新信息,但 not 不会重新触发先前运行的规则。因此,如果以前触发了一条规则,则该规则已经结束并完成。但是,随后的比赛现在将知道新事实。

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

此方法的缺点通常与大多数情况下相同,在大多数情况下,您会带有“标志”或其他种类的信号灯-您正在处理的项目数可能会失控。如果您一次要评估几个产品,那么开销会很低。但是想像一下,您有成千上万的产品同时在系统中运行-可能您的工作内存中可能有大量的String。这也可能导致应用程序和硬件出现资源问题。 (有一个关键点,您应该开始对字符串进行内部实习,这超出了此答案的范围。)

但是对于一个简单的解决方案,此方法“快速又脏”。


解决方案2:收回物品

仅当您不需要产品存储其他任何内容时,此解决方案才有效。也就是说,一旦MyPRODUCT_A触发了您的规则,您就不再需要任何 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,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做同样的事情。他们目前正在推动采用“删除”作为“插入”的对立面,但我们的Drools老用户从后台当天的偏好以“撤回”。)

因此,基本上,您将看到My​​PRODUCT_A的第一个实例符合规则,记录,然后它将从工作内存中删除MyPRODUCT_A(及其本身)的所有其他实例。然后MyPRODUCT_B将遵守规则,并执行相同的操作。最终,工作记忆中将没有产品。

很显然,如果您仍然需要对Product进行其他处理,那么这不是可行的解决方案,因为如果从内存中删除了Products,这些其他规则将无法执行。但是,如果您有一个非常专业的数据集只能执行此操作,则可以采用这种方法。


正如我所说,有许多解决此问题的方法。您的规则保持不变。每个产品的正确触发。如何解决仅针对部分产品的问题是您面临的问题,但是如何准确地解决问题取决于您和您的要求。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...