Optaplanner: Iterate over list variable in drools rule file

守給你的承諾、 提交于 2020-01-06 04:49:26

问题


I am solving a problem similar to employee rostering. I have an additional constraint. The employees have a "type" value assigned to them. It's a hard constraint that atleast 1 employee of each "type" be there everyday. I have modelled it as follows:

rule "All employee types must be covered"
when
    $type: Constants.EmployeeType() from Constants.EmployeeType.values()       
    not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())

then
    scoreHolder.addHardConstraintMatch(kcontext, -100);
end

This rule however, does not consider that the constraint be satisfied on each day. I have a list of date strings. How can I iterate over them in the drools file in the same manner that I am on the EmployeeType enum?

Edit: I figured out a way but it feels like a hack. When initialising the list of date strings, I also assign it to a static variable. Then I am able to use the static variable similar to the enum.

rule "All employee types must be covered"
when
    $type: Constants.EmployeeType() from Constants.EmployeeType.values()
    $date: String() from Constants.dateStringList;   
    not Shift(employeeId != null, $date == getDate(), $employee: getEmployee(), $employee.getType() == $type.getValue())

then
    scoreHolder.addHardConstraintMatch(kcontext, -100);
end

Don't think this is the correct approach though.


回答1:


Your approach works, but having to define dynamic configurations in a static property of a class doesn't sound right (like you pointed out).

One solution would be to either use a global in the session, or to have a fact class that specify this configuration.


Using a global

If you decide to take this approach, then you need to define a global of type List<String> in your DRL and then use it in your rules in combination with the memberOf operator:

global List<String> dates;

rule "All employee types must be covered"
when
  $type: Constants.EmployeeType() from Constants.EmployeeType.values()  
  not Shift(
    employeeId != null, 
    date memberOf dates, 
    $employee: getEmployee(), 
    $employee.getType() == $type.getValue()
  )
then
  scoreHolder.addHardConstraintMatch(kcontext, -100);
end

It is recommended to set the value for global before you insert any fact Shift into you session:

List<String> dates = //get the List from somewhere
ksession.setGlobal("dates", dates);

Using a Fact Class

Other than a global, you can model your configuration as a class. This makes things easier if you want for example to modify the configuration inside the rules themselves.

for this approach you will need to have a class containing the List<String> first. You could in theory insert the List<String> without wrapping it in any class, but this will make things hard to read and maintain.

public class DatesConfiguration { 

    private List<String> dates;

    //... getters + setters
}

Then, you need to instantiate an object of this class and to insert it into your session:

DatesConfiguration dc = new DatesConfiguration();
dc.setDates(...);

ksession.insert(dc);

At this point, the object you have created is just another fact for Drools and can be used in your rules:

rule "All employee types must be covered"
when
  $type: Constants.EmployeeType() from Constants.EmployeeType.values()  
  DatesConfiguration($dates: dates)
  not Shift(
    employeeId != null, 
    date memberOf $dates, 
    $employee: getEmployee(), 
    $employee.getType() == $type.getValue()
  )
then
  scoreHolder.addHardConstraintMatch(kcontext, -100);
end

Hope it helps,



来源:https://stackoverflow.com/questions/54044004/optaplanner-iterate-over-list-variable-in-drools-rule-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!