本例并不适合零基础,对于optalanner不知道基本概念可以先去看看其他的文章。
推荐:OptaPlanner学习记录1_optaplanner 排产-CSDN博客(我也是看完他的之后自己总结的)
问题示例:N皇后问题;在n×n的棋盘上,放置n个皇后,本例中n=8;并且皇后不能在同一行、列或者同一斜线;
①、对象建模:
现在将x坐标与y坐标当做资源作为皇后的属性,皇后作为待分配坐标的类,还需要一个统一所有数据的规划类。
1.设置id基类:
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 10:21
*/
public class AbstractPersistable implements Serializable {
protected Integer id;
protected AbstractPersistable() {
}
protected AbstractPersistable(int id) {
this.id = id;
}
@PlanningId
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return getClass().getName().replaceAll(".*\\.", "") + "-" + id;
}
}
2.坐标X类(资源类):
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 10:21
*/
public class X {
private Integer index;
public X(Integer index) {
this.index = index;
}
public X() {
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
@Override
public String toString() {
return "X{" +
"index=" + index +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
X x = (X) o;
return Objects.equals(index, x.index);
}
@Override
public int hashCode() {
return Objects.hash(index);
}
}
3.坐标Y类(资源类):
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 10:21
*/
public class Y {
private Integer index;
public Y(Integer index) {
this.index = index;
}
public Y(){
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
@Override
public boolean equals(Object o) {
if (this == o) {return true;}
if (o == null || getClass() != o.getClass()) {return false;}
Y y = (Y) o;
return Objects.equals(index, y.index);
}
@Override
public int hashCode() {
return Objects.hash(index);
}
@Override
public String toString() {
return "Y{" +
"index=" + index +
'}';
}
}
4.皇后类(分配后的实体类):
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 10:21
*/
@PlanningEntity
public class Nq extends AbstractPersistable {
@PlanningVariable(valueRangeProviderRefs = {"xRange"})
private X x;
@PlanningVariable(valueRangeProviderRefs = {"yRange"})
private Y y;
public X getX() {
return x;
}
public void setX(X x) {
this.x = x;
}
public Y getY() {
return y;
}
public void setY(Y y) {
this.y = y;
}
public Nq(Integer id) {
super(id);
}
public Nq() {
}
public Nq(X x, Y y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Nq{" +
"x=" + x +
", y=" + y +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Nq nq = (Nq) o;
return Objects.equals(x, nq.x) && Objects.equals(y, nq.y);
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
5.容器分配类(规划类):
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 10:21
*/
@PlanningSolution
public class NqAssignment extends AbstractPersistable {
@PlanningScore
public HardSoftScore score;
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "xRange")
public List<X> xList;
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "yRange")
public List<Y> yList;
@PlanningEntityCollectionProperty
public List<Nq> nqList;
public HardSoftScore getScore() {
return score;
}
public void setScore(HardSoftScore score) {
this.score = score;
}
public NqAssignment() {
}
public NqAssignment(List<X> xList, List<Y> yList, List<Nq> nqList) {
this.xList = xList;
this.yList = yList;
this.nqList = nqList;
}
public List<X> getxList() {
return xList;
}
public void setxList(List<X> xList) {
this.xList = xList;
}
public List<Y> getyList() {
return yList;
}
public void setyList(List<Y> yList) {
this.yList = yList;
}
public List<Nq> getNqList() {
return nqList;
}
public void setNqList(List<Nq> nqList) {
this.nqList = nqList;
}
@Override
public String toString() {
return "NqAssignment{" +
"score=" + score +
", xList=" + xList +
", yList=" + yList +
", nqList=" + nqList +
'}';
}
}
规划类和分配后的实体类必须有ID;建模方式不止这一种,也有其他形式,主要看个人怎么定义规划类和分配后的实体类与资源类
建模部分参考自:OptaPlanner学习记录2-CSDN博客
②、约束:(不存在软约束)
结合上述条件,可得出下列约束:
1.横坐标不能相同;
2.纵坐标不能相同;
3.横纵坐标的差的绝对值不能相同;
4.横纵坐标的和不能相同;
上面约束全部为硬约束,硬约束不能被打破;软约束不应被打破。
③、注解解释
@PlanningId | 维护id属性 |
@PlanningEntity | 表示任务实体类,标注在类上表示最后问题的解会体现在该实体中 |
@PlanningVariable | 标注在属性上,表示规划过程中,这个属性的值将被plan的,即通过调整这个属性来得到不同的方案 |
@PlanningSolution | 标注在类上表示该类包括了所有解决方案,所以该类中的属性必须包含被分配的属性 |
@PlanningScore | 标注在属性上表示分值 |
@ProblemFactCollectionProperty | 标注来属性上,表示所有的规划情况 |
@ValueRangeProvider | id与@PlanningVariable注解中的valueRangeProviderRefs对应,得到的解决方案会分配在被标有@PlanningVariable的属性里 |
@PlanningEntityCollectionProperty | 需要被规划的任务:在本例中表示皇后,因为皇后需要被分配X与Y坐标 |
④、求解器写法:
1.ConstraintProvider写法:增量评分,ConstraintStream编码方式
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 11:19
*/
public class NqAssignmentConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
//1.横坐标不能相同
goConflict(constraintFactory),
//2.纵坐标不能相同
arrangeConflict(constraintFactory),
//3.横纵坐标的差的绝对值不能相同
biasConflict(constraintFactory),
//4.横纵坐标的和不能相同
biasTwoConflict(constraintFactory)
};
}
Constraint goConflict(ConstraintFactory constraintFactory) {
return constraintFactory.fromUniquePair(Nq.class,
//不能在同一行
Joiners.equal(Nq::getX)
)
.penalize("Go conflict", HardSoftScore.ONE_HARD);
}
Constraint biasConflict(ConstraintFactory constraintFactory) {
return constraintFactory.fromUniquePair(Nq.class,
//不能在同一斜线
Joiners.equal((t) -> Math.abs(t.getX().getIndex() - t.getY().getIndex()))
)
.penalize("Bias conflict", HardSoftScore.ONE_HARD);
}
Constraint arrangeConflict(ConstraintFactory constraintFactory) {
return constraintFactory.fromUniquePair(Nq.class,
//不能在同一列
Joiners.equal(Nq::getY)
)
.penalize("Arrange conflict", HardSoftScore.ONE_HARD);
}
Constraint biasTwoConflict(ConstraintFactory constraintFactory) {
return constraintFactory.fromUniquePair(Nq.class,
//不能在同一斜线
Joiners.equal((t) -> t.getX().getIndex() + t.getY().getIndex())
)
.penalize("BiasTwo conflict", HardSoftScore.ONE_HARD);
}
}
2.EasyScoreCalculator写法:简易评分,简单易懂
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 12:58
*/
public class NqAssignmentEasyScoreCalculator implements EasyScoreCalculator<NqAssignment,
HardSoftScore> {
@Override
public HardSoftScore calculateScore(NqAssignment nqAssignment) {
List<Nq> nqList = nqAssignment.nqList;
//硬约束分值
int hardScore = 0;
for (Nq a : nqList) {
for (Nq b : nqList) {
if (a.getId().compareTo(b.getId()) != 0 && a.getX() != null && a.getY() != null && b.getX() != null && b.getY() != null) {
if (a.getX().equals(b.getX())) {
//如果x坐标相同就减分
hardScore--;
}
if (a.getY().equals(b.getY())) {
//如果y坐标相同就减分
hardScore--;
}
if (Math.abs(a.getX().getIndex() - a.getY().getIndex()) == Math.abs(b.getX().getIndex() - b.getY().getIndex())) {
//如果同一斜线就减分
hardScore--;
}
if (a.getX().getIndex()+a.getY().getIndex() == b.getX().getIndex()+b.getY().getIndex()){
//如果同一斜线就减分
hardScore--;
}
}
}
}
//System.out.println("hardScore = " + hardScore);
//软约束分值
int softScore = 0;
return HardSoftScore.of(hardScore, softScore);
}
}
二者并无本质上的区别,都是通过计算分值来筛选出得分最高的,只是写法上有些不同。
ConstraintProvider写法:stream流编码方式。
EasyScoreCalculator写法:简单写法,逻辑一目了然。
还有一种写法:通过Drools规则引擎来描述约束并进行评分。比较古老(现在已经可以完全脱离Drools规则引擎),不推荐使用,想要了解可以查阅资料。
⑤、main方法执行
/**
* @author Xiao Mi Feng
* Created with IntelliJ IDEA
* @date 2024-01-24 14:26
*/
public class NqAssignmentApp {
public static void main(String[] args) {
SolverFactory<NqAssignment> objectSolverFactory = SolverFactory.create(new SolverConfig()
.withSolutionClass(NqAssignment.class)//待规划的容器
.withEntityClasses(Nq.class)//规划后的容器
.withConstraintProviderClass(
NqAssignmentConstraintProvider.class)//与EasyScoreCalculator求解器取其一即可
.withTerminationSpentLimit(Duration.ofSeconds(10))//指定运算时间(s),不指定则默认一直计算
);
NqAssignment checkerboard = generateDemoData();
Solver<NqAssignment> solver = objectSolverFactory.buildSolver();
NqAssignment solve = solver.solve(checkerboard);
System.out.println("solve = " + solve);
}
//设置八个皇后和64个坐标
static NqAssignment generateDemoData() {
int queue = 8;
List<X> xList = new ArrayList<>();
List<Y> yList = new ArrayList<>();
List<Nq> nqList = new ArrayList<>();
for (int i = 1; i <= queue; i++) {
xList.add(new X(i));
yList.add(new Y(i));
nqList.add(new Nq(i));
}
return new NqAssignment(xList, yList, nqList);
}
}
⑥、运行结果
{
"nqList":[
{
"id":1,
"x":{
"index":8
},
"y":{
"index":7
}
},
{
"id":2,
"x":{
"index":7
},
"y":{
"index":2
}
},
{
"id":3,
"x":{
"index":3
},
"y":{
"index":5
}
},
{
"id":4,
"x":{
"index":4
},
"y":{
"index":3
}
},
{
"id":5,
"x":{
"index":6
},
"y":{
"index":6
}
},
{
"id":6,
"x":{
"index":1
},
"y":{
"index":4
}
},
{
"id":7,
"x":{
"index":5
},
"y":{
"index":1
}
},
{
"id":8,
"x":{
"index":2
},
"y":{
"index":8
}
}
]
}
结合上面结果可以得出下表:所有皇后不在同一行,列和同一斜线
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
🤡 | |||||||
🤡 | |||||||
🤡 | |||||||
🤡 | |||||||
🤡 | |||||||
🤡 | |||||||
🤡 | |||||||
🤡 |