Optaplanner/Timefold订单分配场景下库存管理方案问询
库存管理替代实现方案(Optaplanner/Timefold)
方案1:将库存抽象为独立ProblemFact实体
把库存定义成单独的Stock实体,替代原有的Map结构,更贴合Optaplanner的问题事实设计规范,也方便后续扩展库存相关属性(如库存地点、有效期等)。
@ProblemFact public class Stock { private Product product; private Integer availableBoxes; // 构造器、getter/setter方法 }
修改Solution类,将库存集合标记为问题事实:
@Solution public class Solution { @PlanningEntityCollectionProperty private List<Order> orders; @ProblemFactCollectionProperty private List<Vehicle> vehicles; @ProblemFactCollectionProperty private List<Stock> stockList; // 辅助方法:快速查询指定产品的可用库存 public Integer getAvailableStock(Product product) { return stockList.stream() .filter(s -> s.getProduct().equals(product)) .findFirst() .map(Stock::getAvailableBoxes) .orElse(0); } }
这种方式结构清晰,约束编写时可直接基于问题事实集合做累计计算。
方案2:用Shadow变量自动跟踪库存消耗
若需实时维护每个产品的已消耗库存,可为Stock实体添加影子变量,由框架自动更新,避免手动遍历订单计算累计值。
首先在Stock中定义影子变量:
@ProblemFact public class Stock { private Product product; private Integer availableBoxes; @ShadowVariable(sourceVariableName = "vehicle", variableListenerClass = StockConsumptionListener.class) private Integer consumedBoxes; // 计算剩余库存的辅助方法 public Integer getRemainingBoxes() { return availableBoxes - (consumedBoxes == null ? 0 : consumedBoxes); } // getter/setter、构造器 }
实现VariableListener处理库存消耗的更新逻辑:
public class StockConsumptionListener implements VariableListener<Order> { @Override public void afterVariableChanged(ScoreDirector scoreDirector, Order order) { Product product = order.getProduct(); Stock stock = findStockForProduct(scoreDirector, product); if (stock == null) return; // 扣除旧分配带来的库存消耗(若之前已分配车辆) Order oldOrder = (Order) scoreDirector.getWorkingObject(order); if (oldOrder.getVehicle() != null) { scoreDirector.beforeVariableChanged(stock, "consumedBoxes"); stock.setConsumedBoxes(stock.getConsumedBoxes() - oldOrder.getAmountOfBoxes()); scoreDirector.afterVariableChanged(stock, "consumedBoxes"); } // 添加新分配带来的库存消耗 if (order.getVehicle() != null) { scoreDirector.beforeVariableChanged(stock, "consumedBoxes"); stock.setConsumedBoxes(stock.getConsumedBoxes() + order.getAmountOfBoxes()); scoreDirector.afterVariableChanged(stock, "consumedBoxes"); } } private Stock findStockForProduct(ScoreDirector scoreDirector, Product product) { Solution solution = (Solution) scoreDirector.getWorkingSolution(); return solution.getStockList().stream() .filter(s -> s.getProduct().equals(product)) .findFirst() .orElse(null); } // 实现其他必需的VariableListener方法(beforeVariableChanged等,可留空或做对应处理) }
这种方案下,库存消耗由框架自动维护,约束只需判断stock.getRemainingBoxes() >= 0即可,无需手动计算累计值。
方案3:在约束中直接计算累计消耗(无需修改现有结构)
如果不想改动当前实体结构,可直接在约束定义里用Stream API计算指定产品的总消耗,再与库存对比:
public class OrderAssignmentConstraints { @Constraint public Constraint stockAvailability(ConstraintFactory constraintFactory) { return constraintFactory.forEach(Order.class) .groupBy(Order::getProduct, sum(Order::getAmountOfBoxes)) .join(Stock.class, Joiners.equal(Function.identity(), Stock::getProduct)) .filter((productTotalConsumed, stock) -> productTotalConsumed.getSecond() > stock.getAvailableBoxes()) .penalize("Exceeded stock", HardSoftScore.ONE_HARD, (productTotalConsumed, stock) -> productTotalConsumed.getSecond() - stock.getAvailableBoxes()); } }
如果仍用原有的Map<Product, Integer>存储库存,可调整约束逻辑直接从Solution中获取库存值:
return constraintFactory.forEach(Order.class) .groupBy(Order::getProduct, sum(Order::getAmountOfBoxes)) .filter((product, totalConsumed) -> totalConsumed > getStockFromSolution(constraintFactory, product)) .penalize("Exceeded stock", HardSoftScore.ONE_HARD, (product, totalConsumed) -> totalConsumed - getStockFromSolution(constraintFactory, product)); } private Integer getStockFromSolution(ConstraintFactory constraintFactory, Product product) { Solution solution = (Solution) constraintFactory.getSolutionDescriptor().getSolutionClass() .cast(constraintFactory.getScoreDirector().getWorkingSolution()); return solution.getStock().getOrDefault(product, 0); }
关于ValueRangeProviders的说明
ValueRangeProviders的作用是给PlanningVariable提供可选值范围(比如车辆的备选列表),而库存管理属于约束校验范畴,并非变量取值范围的定义,所以不适合用它来实现库存逻辑,这也是你之前尝试没有进展的原因。
内容的提问来源于stack exchange,提问作者Gustavo André
相关产品推荐
相关产品推荐

