官方例子4 评分分配网球
网球俱乐部每天有四个队进行循环赛。把这四个位置公平地分配给各队。
一个团队每天只能玩一次。
有些球队在某些日期不可用。
所有球队都应该(几乎)打相同的次数。
对抗赛:每支球队都应该与其他球队进行相同次数的比赛
如需求,每天最多有四个队伍打比赛,排七天的顺序出来。
这个在官方例子中给出了很多新用法,下边先一一说明
accumulate除了sum,count自带的方法中,还有一个AccumulateFunction接口,是 Drools 中的一个接口,用于定义在规则引擎中累积函数的行为。在规则引擎中,累积函数允许你对多个匹配的事实进行聚合操作,生成一个结果。比如在这个例子中,有两个软约束。每支球队都应该与其他球队进行相同次数的比赛。每天最多有四个队伍打比赛,排七天的顺序出来。这些都是需要全局统计的。这时候自带的方法就无法满足需求了(sum,只是计数,没法全局控制)。
该接口需要实现的方法中,accumulate比较重要,每次调用时,都会计算一个值,用于衡量队伍比赛数量。他与sum等方式不同的地方是,drl现有语法只能获得当前队伍的比赛数量,无法获得全部队伍比赛数量,也就无法平衡整体数量。
package com.example.demo.optaPlanner.solver;
import org.kie.api.runtime.rule.AccumulateFunction;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public class LoadBalanceByCountAccumulateFunction
implements AccumulateFunction<LoadBalanceByCountAccumulateFunction.LoadBalanceByCountData> {
protected static class LoadBalanceByCountData implements Serializable {
private Map<Object, Long> groupCountMap;
// the sum of squared deviation from zero
private long squaredSum;
}
@Override
public LoadBalanceByCountData createContext() {
return new LoadBalanceByCountData();
}
@Override
public void init(LoadBalanceByCountData data) {
data.groupCountMap = new HashMap<>();
data.squaredSum = 0L;
}
@Override
public void accumulate(LoadBalanceByCountData data, Object groupBy) {
long count = data.groupCountMap.compute(groupBy,
(key, value) -> (value == null) ? 1L : value + 1L);
// squaredZeroDeviation = squaredZeroDeviation - (count - 1)² + count²
// <=> squaredZeroDeviation = squaredZeroDeviation + (2 * count - 1)
data.squaredSum += (2 * count - 1);
}
@Override
public boolean supportsReverse() {
return true;
}
@Override
public void reverse(LoadBalanceByCountData data, Object groupBy) {
Long count = data.groupCountMap.compute(groupBy,
(key, value) -> (value.longValue() == 1L) ? null : value - 1L);
data.squaredSum -= (count == null) ? 1L : (2 * count + 1);
}
@Override
public Class<LoadBalanceByCountResult> getResultType() {
return LoadBalanceByCountResult.class;
}
@Override
public LoadBalanceByCountResult getResult(LoadBalanceByCountData data) {
return new LoadBalanceByCountResult(data.squaredSum);
}
@Override
public void writeExternal(ObjectOutput out) {
}
@Override
public void readExternal(ObjectInput in) {
}
public static class LoadBalanceByCountResult implements Serializable {
private final long squaredSum;
public LoadBalanceByCountResult(long squaredSum) {
this.squaredSum = squaredSum;
}
public long getZeroDeviationSquaredSum() {
return squaredSum;
}
/**
* @return {@link #getZeroDeviationSquaredSumRoot(double)} multiplied by {@literal 1 000}
*/
public long getZeroDeviationSquaredSumRootMillis() {
return getZeroDeviationSquaredSumRoot(1_000.0);
}
/**
* @return {@link #getZeroDeviationSquaredSumRoot(double)} multiplied by {@literal 1 000 000}
*/
public long getZeroDeviationSquaredSumRootMicros() {
return getZeroDeviationSquaredSumRoot(1_000_000.0);
}
/**
* @param scaleMultiplier {@code > 0}
* @return {@code >= 0}, {@code latexmath:[f(n) = \sqrt{\sum_{i=1}^{n} (x_i - 0)^2}]} multiplied by scaleMultiplier
*/
public long getZeroDeviationSquaredSumRoot(double scaleMultiplier) {
return (long) (Math.sqrt((double) squaredSum) * scaleMultiplier);
}
}
}
除了这里有新东西以外,如果想让某个队伍固定在某天打比赛,可以使用@PlanningPin注解,为true时,在优化时,不会变动他的内容。
Day类
package com.example.demo.optaPlanner.Tennis;
import com.example.demo.optaPlanner.domain.AbstractPersistable;
public class Day extends AbstractPersistable {
//星期几
public Integer indexDay;
public Day() {
}
public Day(long id, Integer indexDay) {
super(id);
this.indexDay = indexDay;
}
public Integer getIndexDay() {
return indexDay;
}
public void setIndexDay(Integer indexDay) {
this.indexDay = indexDay;
}
}
Team类
package com.example.demo.optaPlanner.Tennis;
import com.example.demo.optaPlanner.domain.AbstractPersistable;
public class Team extends AbstractPersistable {
//队伍名称
public String teamName;
public Team() {
}
public Team(long id, String teamName) {
super(id);
this.teamName = teamName;
}
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
}
TennisAssignment类
package com.example.demo.optaPlanner.Tennis;
import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.entity.PlanningPin;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
@PlanningEntity
public class TennisAssignment extends AbstractPersistable {
public Day day;
public Team team;
public Boolean pinned;
public TennisAssignment(){}
public TennisAssignment(long id, Day day, Team team,Boolean pinned) {
super(id);
this.day = day;
this.team = team;
this.pinned = pinned;
}
public Day getDay() {
return day;
}
public void setDay(Day day) {
this.day = day;
}
@PlanningVariable(valueRangeProviderRefs={"teamRange"})
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
//不可动
@PlanningPin
public Boolean getPinned() {
return this.pinned;
}
public void setPinned(Boolean pinned) {
this.pinned = pinned;
}
}
TennisSolution类
package com.example.demo.optaPlanner.Tennis;
import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import java.util.List;
@PlanningSolution
public class TennisSolution extends AbstractPersistable {
public List<Day> dayList;
public List<Team> teamList;
public List<TennisAssignment> tennisAssignmentList;
public List<UnTennisAssignment> unTennisAssignmentList;
public HardSoftScore score;
@PlanningScore
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
public TennisSolution(){}
public TennisSolution(long id, List<Day> dayList, List<Team> teamList) {
super(id);
this.dayList = dayList;
this.teamList = teamList;
}
public TennisSolution(long id,
List<Day> dayList,
List<Team> teamList,
List<TennisAssignment> tennisAssignmentList,
List<UnTennisAssignment> unTennisAssignmentList) {
super(id);
this.dayList = dayList;
this.teamList = teamList;
this.tennisAssignmentList = tennisAssignmentList;
this.unTennisAssignmentList = unTennisAssignmentList;
}
public List<Day> getDayList() {
return dayList;
}
public void setDayList(List<Day> dayList) {
this.dayList = dayList;
}
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "teamRange")
public List<Team> getTeamList() {
return teamList;
}
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "unTennisAssignmentRange")
public List<UnTennisAssignment> getUnTennisAssignmentList() {
return unTennisAssignmentList;
}
public void setUnTennisAssignmentList(List<UnTennisAssignment> unTennisAssignmentList) {
this.unTennisAssignmentList = unTennisAssignmentList;
}
public void setTeamList(List<Team> teamList) {
this.teamList = teamList;
}
@PlanningEntityCollectionProperty
@ValueRangeProvider(id = "tennisAssignmentRange")
public List<TennisAssignment> getTennisAssignmentList() {
return tennisAssignmentList;
}
public void setTennisAssignmentList(List<TennisAssignment> tennisAssignmentList) {
this.tennisAssignmentList = tennisAssignmentList;
}
}
UnTennisAssignment
package com.example.demo.optaPlanner.Tennis;
import com.example.demo.optaPlanner.domain.AbstractPersistable;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
import org.apache.commons.lang3.tuple.Pair;
/**
* @author wcr
* @create 2023-11-16 17:43
*/
public class UnTennisAssignment extends AbstractPersistable {
//哪些队在哪一天不分配比赛
public Day day;
public Team team;
public UnTennisAssignment(){}
public UnTennisAssignment(long id, Day day, Team team) {
super(id);
this.day = day;
this.team = team;
}
public Day getDay() {
return day;
}
public void setDay(Day day) {
this.day = day;
}
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
}
drl
package com.example.demo.solver;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;
import com.example.demo.optaPlanner.Tennis.Day;
import com.example.demo.optaPlanner.Tennis.Team
import com.example.demo.optaPlanner.Tennis.TennisAssignment
import com.example.demo.optaPlanner.Tennis.TennisSolution
import com.example.demo.optaPlanner.Tennis.UnTennisAssignment;
import accumulate com.example.demo.optaPlanner.solver.LoadBalanceByCountAccumulateFunction loadBalanceByCount;
import org.apache.commons.lang3.tuple.Pair;
global HardSoftScoreHolder scoreHolder;
//一支球队每天只能打一场比赛。
rule "oneDaybyoneTeam"
when
TennisAssignment($id:getId(),getDay() != null,getTeam() != null,$day:getDay().getId(),$team:getTeam().getId())
TennisAssignment($id != getId(),getDay() != null,getTeam() != null,$day == getDay().getId(),$team == getTeam().getId())
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
//某些队伍在某些天不能比赛
rule "UnTennisAssignment"
when
UnTennisAssignment($day:getDay(),$team:getTeam())
TennisAssignment($day == getDay(),$team == getTeam())
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
//所有球队尽可能打相同的场数
rule "fairAssignmentCountPerTeam"
when
accumulate(
TennisAssignment(team != null, $t : team);
$total : loadBalanceByCount($t)
)
then
scoreHolder.addSoftConstraintMatch(kcontext,- (int) $total.getZeroDeviationSquaredSumRootMillis());
end
//球队之间尽可能打相同场数
rule "evenlyConfrontationCount"
when
accumulate(
TennisAssignment(team != null, $t1 : team, $d : day)
and TennisAssignment(team != null, $t1.getId() < team.getId(), $t2 : team, day == $d);
$total : loadBalanceByCount(Pair.of($t1, $t2))
)
then
System.out.println($total);
scoreHolder.addSoftConstraintMatch(kcontext, - (int) $total.getZeroDeviationSquaredSumRootMillis());
end
<?xml version="1.0" encoding="UTF-8"?>
<solver>
<!-- Domain model configuration -->
<entityClass>com.example.demo.optaPlanner.Tennis.TennisAssignment</entityClass>
<solutionClass>com.example.demo.optaPlanner.Tennis.TennisSolution</solutionClass>
<!-- Score configuration -->
<scoreDirectorFactory>
<scoreDrl>rules5.drl</scoreDrl>
<initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
</scoreDirectorFactory>
<!-- Optimization algorithms configuration -->
<termination>
<secondsSpentLimit>10</secondsSpentLimit>
</termination>
<constructionHeuristic>
<constructionHeuristicType>FIRST_FIT</constructionHeuristicType>
</constructionHeuristic>
<localSearch>
<acceptor>
<lateAcceptanceSize>500</lateAcceptanceSize>
<!--<entityTabuSize>7</entityTabuSize>-->
</acceptor>
<forager>
<!--<acceptedCountLimit>1000</acceptedCountLimit>-->
<acceptedCountLimit>1</acceptedCountLimit>
</forager>
</localSearch>
</solver>
package com.example.demo.optaPlanner;
import com.example.demo.optaPlanner.Tennis.*;
import com.example.demo.optaPlanner.nqSolver.nqAssignment;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class AppTennis {
public static void main(String[] args) {
startPlan();
}
private static void startPlan() {
InputStream ins = AppTennis.class.getResourceAsStream("/taskassignmentConfiguration4.xml");
SolverFactory<TennisSolution> solverFactory = SolverFactory.createFromXmlInputStream(ins);
Solver<TennisSolution> solver = solverFactory.buildSolver();
TennisSolution tennisSolution = build();
TennisSolution assigned1 = solver.solve(tennisSolution);//启动引擎
System.out.println("1111111111111111111");
List<TennisAssignment> tennisAssignmentList = assigned1.getTennisAssignmentList();
Map<Object, List<TennisAssignment>> collect = tennisAssignmentList.stream().collect(Collectors.groupingBy(t -> {
return t.getDay().getIndexDay();
}));
for (Map.Entry<Object, List<TennisAssignment>> entry : collect.entrySet()) {
List<Team> team = entry.getValue().stream().map(TennisAssignment::getTeam).collect(Collectors.toList());
String TeamName = team.stream()
.map(Team::getTeamName)
.collect(Collectors.joining("->"));
System.out.println("day:" + entry.getKey() + " " + TeamName + "\n");
}
System.out.println("exit!");
}
public static TennisSolution build() {
//7天 16个队伍
List<Day> dayList = new ArrayList<>();
for (int i = 1; i <= 7; i++) {
dayList.add(new Day((long) i, i));
}
List<Team> teamList = new ArrayList<>();
for (int i = 1; i <= 16; i++) {
teamList.add(new Team((long) i, "team" + i));
}
List<TennisAssignment> tennisAssignmentList = new ArrayList<>();
long id = 1L;
for (int i = 0; i < 7; i++) {
for (int j = 1; j <= 4; j++) {
tennisAssignmentList.add(new TennisAssignment(id++, dayList.get(i), i==1 && j==2 ? teamList.get(2) : null, i==1 && j==2 ? true : false));
}
}
List<UnTennisAssignment> unTennisAssignmentList = new ArrayList<>();
// unTennisAssignmentList.add(new UnTennisAssignment(1L,dayList.get(0),teamList.get(0)));
TennisSolution tennisSolution = new TennisSolution(1L, dayList, teamList, tennisAssignmentList, unTennisAssignmentList);
return tennisSolution;
}
}