如何在OptaPlanner N皇后示例中定义moveFactory?配置后遇异常
你遇到的异常本质是默认MoveSelector和自定义MoveListFactory的冲突——OptaPlanner默认会自动为可规划变量生成一个ChangeMoveSelector,当你直接在配置里加<moveListFactory>却没明确覆盖默认选择器时,系统会同时加载多个MoveSelector,从而触发参数错误。下面是一步步的解决方案:
1. 修正XML配置文件
你需要把<moveListFactory>包裹在<moveSelector>标签内,明确告诉OptaPlanner只使用你的自定义MoveFactory,替换掉默认的选择器。修改后的localSearch配置示例如下:
<solver> <!-- 保留原有的solutionClass、entityClass、scoreDirectorFactory等配置 --> <localSearch> <!-- 明确指定自定义MoveListFactory为唯一的MoveSelector --> <moveSelector> <moveListFactory> <moveListFactoryClass>org.optaplanner.examples.nqueens.solver.move.factory.RowChangeMoveFactory</moveListFactoryClass> </moveListFactory> </moveSelector> <!-- 保留原有的acceptor、forager等优化配置 --> <acceptor> <entityTabuSize>7</entityTabuSize> </acceptor> <forager> <acceptedCountLimit>1000</acceptedCountLimit> </forager> </localSearch> </solver>
2. 正确实现RowChangeMoveFactory
你的RowChangeMoveFactory需要实现OptaPlanner的MoveListFactory接口,针对N皇后场景生成所有合法的行变更移动(即把某一列的皇后移动到其他行,保持列不变)。示例代码如下:
package org.optaplanner.examples.nqueens.solver.move.factory; import org.optaplanner.core.impl.heuristic.move.Move; import org.optaplanner.core.impl.heuristic.move.factory.MoveListFactory; import org.optaplanner.examples.nqueens.domain.NQueen; import org.optaplanner.examples.nqueens.domain.NQueensSolution; import java.util.ArrayList; import java.util.List; public class RowChangeMoveFactory implements MoveListFactory<NQueensSolution> { @Override public List<? extends Move> createMoveList(NQueensSolution solution) { List<Move> moveList = new ArrayList<>(); int boardSize = solution.getN(); // 遍历每个皇后,生成所有可行的行变更移动 for (NQueen queen : solution.getNQueenList()) { int currentRow = queen.getRowIndex(); for (int targetRow = 0; targetRow < boardSize; targetRow++) { // 排除当前行,避免无效移动 if (targetRow != currentRow) { moveList.add(new RowChangeMove(queen, targetRow)); } } } return moveList; } }
3. 实现对应的RowChangeMove类
MoveFactory生成的每一个Move实例需要支持执行、撤销操作,还要能判断移动是否有效。你需要实现这个Move类:
package org.optaplanner.examples.nqueens.solver.move; import org.optaplanner.core.impl.heuristic.move.AbstractMove; import org.optaplanner.examples.nqueens.domain.NQueen; import org.optaplanner.examples.nqueens.domain.NQueensSolution; public class RowChangeMove extends AbstractMove<NQueensSolution> { private final NQueen queen; private final int targetRowIndex; public RowChangeMove(NQueen queen, int targetRowIndex) { this.queen = queen; this.targetRowIndex = targetRowIndex; } // 执行移动:修改皇后的行号 @Override protected void doMoveOnGenuineVariables(NQueensSolution solution) { queen.setRowIndex(targetRowIndex); } // 创建撤销移动:回到原来的行号 @Override public RowChangeMove createUndoMove(NQueensSolution solution) { return new RowChangeMove(queen, queen.getRowIndex()); } // 判断移动是否有效(目标行和当前行不同) @Override public boolean isMoveDoable(NQueensSolution solution) { return targetRowIndex != queen.getRowIndex(); } // 生成移动描述,方便日志调试 @Override public String getSimpleMoveDescription() { return queen + " moves from row " + queen.getRowIndex() + " to row " + targetRowIndex; } }
异常原因复盘
OptaPlanner默认会自动为实体的可规划变量生成ChangeMoveSelector,当你直接添加<moveListFactory>却没通过<moveSelector>包裹时,系统会同时加载默认选择器和自定义工厂,导致moveSelector列表存在多个元素,最终触发IllegalArgumentException。通过将<moveListFactory>放入<moveSelector>内,你就明确指定了唯一的Move选择器,解决了冲突。
内容的提问来源于stack exchange,提问作者lambda-pumpkin

