I have some doubts about how GroupBy works in a MultiConstraintStream. I need to group entries by two fields at the same time, and I am unsure how to do it.
For context, I am trying to build a constraint in Optaplanner for a job scheduling problem. I want to limit the maximum amount of output that can be done per day for each different type of job.
The constraint would go like this...
private Constraint MaximumDailyOuput(ConstraintFactory constraintFactory) {
// Limits maximum output per day.
return constraintFactory.forEach(TimeSlotOpta.class) // iterate for each timeslot (days)
// join time slots with jobs
.join(JobOpta.class)
// filter if jobs are being done that day
.filter((timeslot, job) -> job.isActive(timeslot.getDay()))
// join with job types, and filter, not sure if this is necessary or optimal
.join(JobTypeOpta.class)
.filter((timeSlot, job, jobType) -> job.getJobType() == jobType)
// HERE: now I would like to group the jobs that are active
// during a time slot and that are of the same type (job.getJobType()).
// For each group obtained, I need to sum the outputs of the jobs,
// which can be obtained using job.getDailyOutput().
// Therefore, for each day (timeslot) and for each job type,
// I should obtain a sum that cannot overcome
// the daily maximum for that job type (jobType.getMaximumDailyOuput())
.groupBy((timeSlot, job, jobType) -> ...)
...
.penalize("Maximum daily output exceeded", HardMediumSoftScore.ONE_HARD,
(timeSlot, jobType, dailyOuput) -> dailyOuput - jobType.getMaximumDailyOutput());
}
You can do this by specifying multiple group key functions in your groupBy
private Constraint MaximumDailyOuput(ConstraintFactory constraintFactory) {
// Limits maximum output per day.
return constraintFactory.forEach(TimeSlotOpta.class) // iterate for each timeslot (days)
// join time slots with jobs
.join(JobOpta.class)
// filter if jobs are being done that day
.filter((timeslot, job) -> job.isActive(timeslot.getDay()))
// join with job types, and filter, not sure if this is necessary or optimal
.join(JobTypeOpta.class)
.filter((timeSlot, job, jobType) -> job.getJobType() == jobType)
// calculate total output for a given timeslot of a given jobType
.groupBy((timeSlot, job, jobType) -> timeslot,
(timeSlot, job, jobType) -> jobType,
ConstraintCollectors.sum((timeSlot, job, jobType) -> job.getDailyOutput()))
// include only timeslot/jobType pairs where dailyOutput exceeds maximum allowed
.filter((timeSlot, jobType, dailyOuput) -> dailyOuput > jobType.getMaximumDailyOutput())
.penalize("Maximum daily output exceeded", HardMediumSoftScore.ONE_HARD,
(timeSlot, jobType, dailyOuput) -> dailyOuput - jobType.getMaximumDailyOutput());
}
For groupBy
, you can include up to four group key functions and collectors combined (if you have 1 key function, you can have up to 3 collectors; if you have 2 key functions, you can have up to 2 collectors, etc.). The key functions are always before the collectors.