Проблемы при попытке собрать вложенные списки в Drools

#drools

#drools

Вопрос:

Я сталкиваюсь с некоторыми трудностями, пытаясь написать правило с помощью Drools (6.4.Final). Либо мое правило не совсем правильное, либо я заканчиваю тем, что пишу несколько правил для одной и той же цели, но без убежденности. Я хотел бы узнать ваше мнение, основанное на вашем опыте и / или рекомендациях, чтобы помочь мне.

У меня есть факт (тип C), который содержит список E.

Каждый E :

  • имеет атрибут int, который равен году
  • может иметь список R

Каждый R :

  • имеет тип
  • имеет определенную сумму

Я хочу проверять, для одного / каждого года, превышает ли сумма сумм R определенного типа значение. Если оно превышает, я хочу вставить факт (X) со всеми E, которые находятся позади.

Моя первая попытка была :

 rule "Sum of R exceed for one year"
when
   C( $listOfE : listOfE )

// Pick one E
   E( $year : year ) from $listOfE

// All Es of the same year
// "collectLists" is a custom accumulate function that simply adds all the elements of a list
  accumulate ( E(year == $year, $listofR : listofR ) from $listOfE;
            $allR : collectLists($listofR))

  // Check the sum
  // "sumbd" is  a custom accumulate function that simply adds BigDecimal (sum is broken for that in 6.4)
  accumulate ( R( type == « X », $amount : amount ) from $allR;
               $sum : sumbd($amount);
               $cumulAnnee > 20000)

then
  insert( new X($year, $elements));
end
 

Это работает, но есть проблема с элементами в X, которые могут не иметь R или иметь R, которые не соответствуют ожидаемому типу…

Моя вторая попытка — это:

Несколько, но более простых правил, объявляющих тип, чтобы помочь накапливать данные и работать с одним E.

 declare YearAccumulation
  year : int
  allE : java.util.List
  sum : BigDecimal
end

rule "Sum of R exceed for one year (1)"
when
   C( $listOfE : listOfE ) 
   $e : E( $year : year ) from $listOfE 

   // No accumulation for this year yet
   not(YearAccumulation( year == $year ))

   accumulate ( R( type == « X », $amount : amount ) from $e.listOfR;
               $amounts : collectList($amount),
               $sum : sumbd($amount);
               $amounts.size > 0)

then
   YearAccumulation ya = new YearAccumulation();
   ya.setYear($year);
   List allE = new ArrayList();
   allE.add($element);
   ya.setAllE(allE);
   ya.setSum($sum);
   insert(ya);
end

rule "Sum of R exceed for one year (2)"
when
   C( $listOfE : listOfE )       
   $e : E( $year : year ) from $listOfE

   accumulate ( R( type == « X », $amount : amount ) from $e.listOfR;
               $amounts : collectList($amount),
               $sum : sumbd($amount);
               $amounts.size > 0)
   // Do not process already processed E
   $ya : YearAccumulation ( year == $year, $e not memberOf allE)
then
   $ya.getAllE().add($e);
   $ya.setSum($ya.getSum().add($sum)); 
   update($ya);
end

rule "Sum of R exceed for one year (3)"
when
   YearAccumulation ($year:year, $elements:allE, sum > 20000)
then
   insert( new X($year, $elements));
end
 

Но я не чувствую уверенности в этом решении, и получение результата выглядит (довольно) сложным.
В конце концов, я хотел бы иметь простое правило (правила), которое будет легко понять и поддерживать.

Спасибо

Ответ №1:

Простым подходом было бы собрать все суммы за год за один проход по всем E и R и вернуть a Map<YearType,BigDecimal> , где YearType объединяет год и тип, и вы можете извлечь объекты ввода для проверки BigDecimal и т. Д. И т. Д.

Я не уверен, можете ли вы написать пользовательскую функцию накопления, возвращающую карту, но это можно сделать, используя «традиционный» синтаксис накопления, где у вас есть предложения init, action и result для программирования всего, что вам нужно.

Это краткая версия накопителя, который вы можете использовать:

 Map() from accumulate( E( $lr: listOfR != null, $y: year )
                       init( Map m = new HashMap(); )
                       action( for( int i = 0; i < $lr.size();   i ){
                                 //... add to m.get($y) observing R's type
                               } )
                       result( m ) )
 

Комментарии:

1. Спасибо. Это был бы действительно правильный путь. Но, похоже, это больше похоже на мою первую попытку.

2. Я имею в виду, что вы не проверяете тип R и теряете информацию о том, какие Es имеют учитываемые Rs.

3. Я не думаю, что есть проблема с составлением ключа карты из года и типа. Я добавлю это к своему ответу.

4. Такие алгоритмы могут быть лучше в Java, реализованные в классе C, например Map<YearType,BigDecimal> auditOfR , поскольку не похоже, что вычисление сумм зависит от изменчивых бизнес-правил и должно быть включено в правила. Оценка — годы и типы с чрезмерными суммами — все еще возможна в рамках правил.

5. Даже если вам кажется, что вы забываете о необходимости также отслеживать все ЭС, которые в конечном итоге имеют эти Rs, имеющие правильный тип и имеющие совокупную сумму, превышающую значение (20000). Я понимаю вашу точку зрения.