如何在OptaPlanner中动态定义PlanningVariable(如Map形式)
实现带动态PlanningVariable的可复用PlanningEntity
原代码的问题
你的实现思路存在几个核心问题,导致OptaPlanner无法正常工作:
- Map不能直接作为PlanningVariable:OptaPlanner需要独立追踪每个规划变量的变化(比如单独修改教室或时间段),而将整个Map作为单个变量会让OptaPlanner只能替换整个Map,无法对单个属性进行调整。
- ValueRangeProvider配置不匹配:你的
planningVariableMapRange提供的是Map<String, List<Object>>,但OptaPlanner期望每个规划变量的范围是该变量可能取值的集合,而非键值对结构。
正确实现方案:使用程序化领域配置
要实现可复用的动态规划实体,需要借助OptaPlanner的程序化领域配置API(而非静态注解),这样可以在不同项目中动态定义所需的规划变量,同时复用同一个实体类。
1. 定义通用实体类
创建一个通用实体类,用Map存储动态属性:
import java.util.HashMap; import java.util.Map; public class GenericPlanningEntity { private Long id; private Map<String, Object> properties = new HashMap<>(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Object getProperty(String key) { return properties.get(key); } public void setProperty(String key, Object value) { properties.put(key, value); } }
2. 定义通用解决方案类
同样,创建通用解决方案类,用于存储实体集合和各类问题事实:
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import java.util.List; public class GenericPlanningSolution { private List<GenericPlanningEntity> entityList; // 存储各类问题事实,比如教室、时间段等 private List<?> roomList; private List<?> timeslotList; private HardSoftScore score; // Getters and Setters public List<GenericPlanningEntity> getEntityList() { return entityList; } public void setEntityList(List<GenericPlanningEntity> entityList) { this.entityList = entityList; } public List<?> getRoomList() { return roomList; } public void setRoomList(List<?> roomList) { this.roomList = roomList; } public List<?> getTimeslotList() { return timeslotList; } public void setTimeslotList(List<?> timeslotList) { this.timeslotList = timeslotList; } public HardSoftScore getScore() { return score; } public void setScore(HardSoftScore score) { this.score = score; } }
3. 程序化配置领域模型
在每个项目中,通过程序化API动态定义规划变量和值范围:
import org.optaplanner.core.api.config.domain.EntityConfigurer; import org.optaplanner.core.api.config.domain.DomainConfigurer; import org.optaplanner.core.api.config.domain.SolutionConfigurer; import org.optaplanner.core.api.config.solver.SolverConfig; import org.optaplanner.core.api.solver.SolverFactory; public class DynamicDomainConfig { public static SolverFactory<GenericPlanningSolution> createSolverFactory() { // 创建领域配置器 DomainConfigurer domainConfigurer = SolverConfig.create().domainConfigurer(); // 配置通用实体类 EntityConfigurer<GenericPlanningEntity> entityConfigurer = domainConfigurer.entityConfigurer(GenericPlanningEntity.class); entityConfigurer.planningId("id"); // 指定ID属性 // 动态添加规划变量:教室 entityConfigurer.planningVariableConfigurer("room") .valueRangeProviderRefs("roomRange") .getter(entity -> entity.getProperty("room")) .setter((entity, value) -> entity.setProperty("room", value)); // 动态添加规划变量:时间段 entityConfigurer.planningVariableConfigurer("timeslot") .valueRangeProviderRefs("timeslotRange") .getter(entity -> entity.getProperty("timeslot")) .setter((entity, value) -> entity.setProperty("timeslot", value)); // 配置通用解决方案类 SolutionConfigurer<GenericPlanningSolution> solutionConfigurer = domainConfigurer.solutionConfigurer(GenericPlanningSolution.class); solutionConfigurer.planningEntityCollectionProperty("entityList"); solutionConfigurer.score(HardSoftScore.class); // 配置值范围提供者 solutionConfigurer.valueRangeProviderConfigurer("roomRange") .property("roomList"); solutionConfigurer.valueRangeProviderConfigurer("timeslotRange") .property("timeslotList"); // 构建SolverFactory return SolverFactory.create(domainConfigurer.build()); } }
4. 编写项目专属的约束规则
每个项目需要根据自身业务规则编写ConstraintProvider,比如:
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.score.stream.Constraint; import org.optaplanner.core.api.score.stream.ConstraintFactory; import org.optaplanner.core.api.score.stream.ConstraintProvider; public class ProjectConstraintProvider implements ConstraintProvider { @Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { return new Constraint[]{ // 示例:同一时间同一教室不能有两个课程 roomConflict(constraintFactory), // 其他业务约束... }; } private Constraint roomConflict(ConstraintFactory constraintFactory) { return constraintFactory.from(GenericPlanningEntity.class) .join(GenericPlanningEntity.class, equal(entity -> entity.getProperty("room")), equal(entity -> entity.getProperty("timeslot")), lessThan(GenericPlanningEntity::getId)) .penalize("Room conflict", HardSoftScore.ONE_HARD); } }
关键说明
- 灵活性:通过程序化API,你可以在不同项目中定义不同的规划变量(比如添加
teacher变量),无需修改通用实体类。 - 性能:OptaPlanner对程序化配置的支持和注解配置一样高效,不会影响求解性能。
- 可维护性:通用实体类负责基础属性存储,项目专属配置负责业务相关的变量和约束,职责分离清晰。
内容的提问来源于stack exchange,提问作者Bourne Wang
相关产品推荐
相关产品推荐

