这是一个官方的例子:假设现在有N个进程需要N台电脑去维护,且每台电脑配置可能不一样。每台电脑不管维护多少进程,他们的维护费用一致;进程所需的CPU功率之和与所需内存之和不能超过电脑的CPU功率与内存容量。求怎样分配能够降低维护成本。
图片示例:
一、开始建模。
jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
①、资源对象
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 14:14
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
public class Computer extends AbstractPersistable {
/**
* cpu功率
*/
private Integer cpuPower;
/**
* 内存容量
*/
private Integer memoryCapacity;
/**
* 网络容量
*/
private Integer networkCapacity;
/**
* 维护费用
*/
private Integer cost;
}
②、问题解决对象(包含资源对象)
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 14:22
*/
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@PlanningEntity
public class Processes extends AbstractPersistable {
/**
* 该进程需要的cpu
*/
private Integer cpu;
/**
* 该进程需要的内存
*/
private Integer ram;
@PlanningVariable(valueRangeProviderRefs = {"computerRange"})
private Computer computer;
}
③、规划类
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 14:32
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@PlanningSolution
@SuperBuilder
public class CouldBalancePlan extends AbstractPersistable {
/**
* 分值
*/
@PlanningScore
public HardSoftScore score;
/**
* 资源类、问题解决类(进程类)集合
*/
@PlanningEntityCollectionProperty
private List<Processes> processesList;
/**
* 资源类(电脑类)集合
*/
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "computerRange")
private List<Computer> computerList;
}
建模方式并不唯一,另一种方式我会在接下来的案例中给出
二、约束建立
本例中我们可以得到两个硬约束,一个软约束;硬约束不可被打破,软约束尽量不要打破。
①、硬约束:
1、进程所需CPU功率之和不能超过电脑的CPU功率;
2、进程所需的内存之和不能超过电脑的内存容量。
②、软约束:
每台电脑不管维护多少个进程,他的维护费用都是一致的,尽量节省维护成本。
三、求解器
①、ConstraintStream求解器:
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 14:39
*/
public class CouldBalanceConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
cpuConflict(constraintFactory),
ramConflict(constraintFactory),
costConflict(constraintFactory)
};
}
/*****************************************************硬约束*********************************************************************/
private Constraint cpuConflict(ConstraintFactory constraintFactory) {
//接下来的代码有点类似于Stream流
return constraintFactory
//创建流
.from(Processes.class)
//进行分组
.groupBy(Processes::getComputer, ConstraintCollectors.sum(Processes::getCpu))
//过滤出不满足电脑CPU>进程CPU之和的情况
.filter((c, sumCpu) -> c.getCpuPower() < sumCpu)
//进行扣分;关闭流
.penalize("Cpu conflict", HardSoftScore.ONE_HARD);
}
private Constraint ramConflict(ConstraintFactory constraintFactory) {
return constraintFactory.from(Processes.class)
//进行分组
.groupBy(Processes::getComputer, ConstraintCollectors.sum(Processes::getRam))
//过滤出不满足电脑内存>进程内存之和的情况
.filter((c, sumRam) -> c.getMemoryCapacity() < sumRam)
//进行扣分
.penalize("Ram conflict", HardSoftScore.ONE_HARD);
}
/*****************************************************软约束****************************************************************************/
private Constraint costConflict(ConstraintFactory constraintFactory) {
return constraintFactory.from(Processes.class)
//分组
.groupBy(Processes::getComputer)
//去重
.distinct()
//扣分
.penalize("Cost Conflict", HardSoftScore.ONE_SOFT);
}
}
②、Easy求解器
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 14:42
*/
public class CouldBalanceEasyScoreCalculator implements EasyScoreCalculator<CouldBalancePlan,
HardSoftScore> {
@Override
public HardSoftScore calculateScore(CouldBalancePlan couldBalancePlan) {
//硬约束分值
int hardScore = 0;
//软约束分值
int softScore = 0;
//这种写法带有局限性,
// 当一台电脑能包揽所有进程的时候显得 不够贪婪(可能是本人太菜,思考不出合理的逻辑),
// 后期不考虑使用,在这里只是给大家演示约束的逻辑
List<Processes> list = couldBalancePlan.getProcessesList();
//硬约束计算:只计算了CPU和内存
for (Processes a : list) {
for (Processes b : list) {
if (!a.getId().equals(b.getId())) {
if (ObjectUtil.isNotNull(a.getComputer()) && ObjectUtil.isNotNull(b.getComputer())) {
if (a.getComputer().equals(b.getComputer())) {
int cpu = a.getCpu() + b.getCpu();
int ram = a.getRam() + b.getRam();
if (cpu > a.getComputer().getCpuPower() || ram > b.getComputer().getMemoryCapacity()) {
//如果进程的CPU之和大于电脑的CPU或者进程的内存之后大于电脑的内存
hardScore--;
}
if (cpu == a.getComputer().getCpuPower() && ram == a.getComputer().getMemoryCapacity()) {
//如果进程的CPU之和等于电脑的CPU并且进程的内存之和等于电脑的内存
hardScore++;
}
}
}
}
}
}
//软约束:想要把成本降到最低(每台电脑,不管维护多少个进程,它的维护费用都是一样的)
List<Computer> collect = list.stream().map(Processes::getComputer).filter(ObjectUtil::isNotNull).distinct().collect(Collectors.toList());
softScore = softScore - collect.size();
return HardSoftScore.of(hardScore, softScore);
}
}
这里的Easy求解器有一个问题,当一台电脑能包揽所有进程的时候最终结果还是两台电脑。原因是因为for循环的问题。解决方案可以自行探究,欢迎大家评论。
四、求解
/**
* @author Xiao Mi feng
* Created with IntelliJ IDEA
* @date 2024-01-26 15:16
*/
public class CouldBalanceApp {
public static void main(String[] args) throws JsonProcessingException {
SolverFactory<CouldBalancePlan> objectSolverFactory = SolverFactory.create(new SolverConfig()
.withSolutionClass(CouldBalancePlan.class)
.withEntityClasses(Processes.class)
.withConstraintProviderClass(
CouldBalanceConstraintProvider.class)
.withTerminationSpentLimit(Duration.ofSeconds(10))
);
CouldBalancePlan couldBalancePlan = generateDemoData();
CouldBalancePlan solve = objectSolverFactory.buildSolver().solve(couldBalancePlan);
String string = new ObjectMapper().writeValueAsString(solve);
System.out.println("string = " + string);
}
private static CouldBalancePlan generateDemoData() {
List<Computer> computerList = new ArrayList<>();
List<Processes> processesList = new ArrayList<>();
computerList.add(Computer.builder().cpuPower(6).memoryCapacity(6).id(1).cost(1).build());
computerList.add(Computer.builder().cpuPower(7).memoryCapacity(6).id(2).cost(1).build());
computerList.add(Computer.builder().cpuPower(5).memoryCapacity(6).id(3).cost(1).build());
computerList.add(Computer.builder().cpuPower(4).memoryCapacity(6).id(4).cost(1).build());
//这里设置一台能够包揽所有进程的电脑,大家可以看下EasyScore和Constraint的区别,如果能够解决这个问题欢迎大家留言
//computerList.add(Computer.builder().cpuPower(30).memoryCapacity(30).id(5).cost(1).build());
processesList.add(Processes.builder().id(1).cpu(5).ram(5).build());
processesList.add(Processes.builder().id(2).cpu(4).ram(3).build());
processesList.add(Processes.builder().id(3).cpu(2).ram(3).build());
processesList.add(Processes.builder().id(4).cpu(2).ram(1).build());
return CouldBalancePlan.builder().computerList(computerList).processesList(processesList).build();
}
}
五、最终解决方案
{
"processesList":[
{
"id":1,
"cpu":5,
"ram":5,
"computer":{
"id":2,
"cpuPower":7,
"memoryCapacity":6,
"networkCapacity":null,
"cost":1
}
},
{
"id":2,
"cpu":4,
"ram":3,
"computer":{
"id":1,
"cpuPower":6,
"memoryCapacity":6,
"networkCapacity":null,
"cost":1
}
},
{
"id":3,
"cpu":2,
"ram":3,
"computer":{
"id":1,
"cpuPower":6,
"memoryCapacity":6,
"networkCapacity":null,
"cost":1
}
},
{
"id":4,
"cpu":2,
"ram":1,
"computer":{
"id":2,
"cpuPower":7,
"memoryCapacity":6,
"networkCapacity":null,
"cost":1
}
}
]
}
注:最重要的是建模思路,第二种建模思路我放在下一篇