LoginSignup
2
0

【Drools】DRLのaccumulate・groupby構文で複数データを扱う

Last updated at Posted at 2023-07-18

DroolsではDRLという記法を用いて、アプリケーション内で実行するルールを記述できます。
DRL記法のなかには、ルールエンジンに投入されたデータを効率的に扱うために様々な構文がサポートされています。
本記事ではaccumulate, groupbyという二つの構文について、サンプルコードを交えて紹介していきます。

はじめに

Droolsではルールの定義に、以下のようなDRL記法を使用します。
when節にはルールの条件を、then節にはルールの条件にマッチした場合に実行するアクションを記載します。

rule "Child"
    when
        $person : Person( age < 20 )
        $drink : Drink()
    then
        $drink.setName( "Orange Juice" );
        $drink.setCharge( 100 );
end

この例は、「年齢が20未満のPersonオブジェクトと、Drinkオブジェクトがルールに渡された」ときに、「JavaのSetterメソッドでデータの更新を行う」というルールです。

単純なデータ更新ではこのような記述で実現可能ですが、実際の業務ではもっと複雑なデータ処理をするケースもあると思います。
DRLでは、複数のデータの集約・集計が行えるように、accumlate, groupbyという構文がサポートされています。

accumulate

ルールエンジンに渡された複数のデータを集約して、一括で処理を行うための構文です。
以下のような形式で記述します。

accumulate( SOURCE_PATTERN; FUNCTIONS [;CONSTRAINTS] )

SOURCE_PATTERN: accumulate処理の対象とするデータを設定します
FUNCTIONS: SOURCE_PATTERNのデータに対して実施する処理を設定します(average(平均値の計算), min(最小値の計算), max(最大値の計算)、等が利用可能です)
CONSTRAINTS: FUNCTIONSの結果を使ったルールの条件を記述できます(任意項目)

groupby

groupbyは、Drools version 8.41.0.FINALから新たに追加された構文です。
ルールエンジンに渡されたデータをなんらかの属性でグループ分けし、各グループごとにaccumulate処理を実行することができます。
以下の形式で記述します。

groupby( SOURCE_PATTERN; KEY_FUNCTION ; FUNCTIONS [;CONSTRAINTS] )

SOURCE_PATTERN: groupby構文で取り扱いたいデータを設定します
KEY_FUNCTION: SOURCE_PATTERNのデータをグループ分けするために、キーとして利用したいデータを設定します
FUNCTIONS : グループ分けしたSOURCE_PATTERNのデータに対して、実施する処理を設定します(accumulateのFUNCTIONSで使用できる関数が利用可能です)
CONSTRAINTS : FUNCTIONSの結果を使ったルールの条件を記述できます(任意項目)

サンプルコード

ここからは、accumulate, groupbyを使用したサンプルコードを紹介していきます。
学生の試験結果の平均点を集計する題材にしています。
コード全量はGithubにあげています。

まず、学生の試験結果を保持するJavaクラスを作成します。
学生の名前・学級・試験結果の点数をフィールドとして定義しています。

StudentTestScore.java
package org.example.drlgroupby;

public class StudentTestScore {

    private String studentName;

    private String studentClass;

    private int testScore;
...

accumulateでテストの平均点を集計する

渡された試験結果から平均点を計算するルールを記述します。

AccumulateSample.drl
import org.example.drlgroupby.StudentTestScore

rule "Calculate overall average"
    when
        accumulate( StudentTestScore( $score : testScore );
                        $avg : average( $score ) )
    then
        System.out.println("Overall Average: " + $avg);
end

accumulate構文では、取り扱うデータとしてStudentTestScoreを指定しており、average()関数を使って平均点を計算するようにしています。
then節で計算した平均点を標準出力します。

テストコードでルールを実行してみましょう。

AccumulateSampleTest.java
...
    @Test
    public void test_学年全体の平均点を計算する() {

        // set up
        var taro = new StudentTestScore("Taro", "A", 100);
        var jiro = new StudentTestScore("Jiro", "A", 75);
        var saburo = new StudentTestScore("Saburo", "B", 70);
        kieSession.insert(taro);
        kieSession.insert(jiro);
        kieSession.insert(saburo);

        // execute
        int count = kieSession.fireAllRules();

        // assert
        assertEquals(count, 2);

    }
...

ここでは3人の学生の試験結果をKieSessionに投入しています。
テストを実行すると、以下の文言が標準出力されます。

Overall Average: 81.66666666666667

3人分の試験結果の平均点が算出されていることがわかります。

groupbyで平均点が80点以上の学級を特定する

続いて、groupbyを用いて学級ごとに平均点を計算し、計算結果が80点を超えている学級を特定してみましょう。
ルールは以下のように定義します。

GroupbySample.drl
import org.example.drlgroupby.StudentTestScore

rule "Filter classes whose average is greater than 80"
    when
        groupby(
            $s : StudentTestScore( $score : testScore );
            $group : $s.getStudentClass();
            $ave : average($score);
            $ave > 80)
    then
        System.out.println("Class " + $group + " average is greater than 80.");
end

groupbyの対象データとしてStudentTestScoreを指定しているのはaccumulateと同様です。
KEY_FUNCTIONとして、$group : $s.getStudentClass();と記述することで、StudentTestScoreクラスのstudentClass(学級)フィールドでデータのグルーピングができるようにしています。
さらにFUNCTIONSとしてaverage関数を記述しているため、各グループごとに平均値が計算されます。
最後に、CONSTRAINTSとして$ave > 80を設定しています。
このようにすることで、前段で計算した平均値が80を超えている学級に対してのみ、then節のアクションを実行できます。

では、テストコードでルールを実行します。
ここでは学級Aの生徒二人、学級Bの生徒二人の試験結果をKieSessionに投入しています。
学級Aの平均点が80点超え、学級Bの平均点は80点未満となるようなデータとしています。

GroupbySampleTest.java
...
    @Test
    public void test_平均点が80点超のクラスを抽出する() {

        // set up
        var taro = new StudentTestScore("Taro", "A", 100);
        var jiro = new StudentTestScore("Jiro", "A", 75);
        var saburo = new StudentTestScore("Saburo", "B", 89);
        var shiro = new StudentTestScore("shiro", "B", 60);
        kieSession.insert(taro);
        kieSession.insert(jiro);
        kieSession.insert(saburo);
        kieSession.insert(shiro);

        // execute
        int count = kieSession.fireAllRules();

        // assert
        assertEquals(count, 2);

    }
...

テストを実行すると、以下の文言がコンソール出力されます。

Class A average is greater than 80.

想定どおり、学級Aの平均が80点超である旨のメッセージが表示されていますね。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0