//============================================================================= // Copyright 2006-2010 Daniel W. Dyer // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //============================================================================= package org.uncommons.watchmaker.examples.sudoku; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.uncommons.maths.number.ConstantGenerator; import org.uncommons.maths.number.NumberGenerator; import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** * Mutates rows in a potential Sudoku solution by manipulating the order * of non-fixed cells in much the same way as the * {@link org.uncommons.watchmaker.framework.operators.ListOrderMutation} * operator does in the Travelling Salesman example. * @author Daniel Dyer */ public class SudokuRowMutation implements EvolutionaryOperator<Sudoku> { private final NumberGenerator<Integer> mutationCountVariable; private final NumberGenerator<Integer> mutationAmountVariable; // These look-up tables keep track of which values are fixed in which columns // and sub-grids. Because the values are fixed, they are the same for all // potential solutions, so we cache the information here to minimise the amount // of processing that needs to be done for each mutation. There is no need to // worry about rows since the mutation ensures that rows are always valid. private final boolean[][] columnFixedValues = new boolean[Sudoku.SIZE][Sudoku.SIZE]; private final boolean[][] subGridFixedValues = new boolean[Sudoku.SIZE][Sudoku.SIZE]; private boolean cached = false; /** * Default is one mutation per candidate. */ public SudokuRowMutation() { this(1, 1); } /** * @param mutationCount The constant number of mutations * to apply to each row in a Sudoku solution. * @param mutationAmount The constant number of positions by * which a list element will be displaced as a result of mutation. */ public SudokuRowMutation(int mutationCount, int mutationAmount) { this(new ConstantGenerator<Integer>(mutationCount), new ConstantGenerator<Integer>(mutationAmount)); if (mutationCount < 1) { throw new IllegalArgumentException("Mutation count must be at least 1."); } else if (mutationAmount < 1) { throw new IllegalArgumentException("Mutation amount must be at least 1."); } } /** * Typically the mutation count will be from a Poisson distribution. * The mutation amount can be from any discrete probability distribution * and can include negative values. * @param mutationCount A random variable that provides a number * of mutations that will be applied to each row in an individual. * @param mutationAmount A random variable that provides a number * of positions by which to displace an element when mutating. */ public SudokuRowMutation(NumberGenerator<Integer> mutationCount, NumberGenerator<Integer> mutationAmount) { this.mutationCountVariable = mutationCount; this.mutationAmountVariable = mutationAmount; } public List<Sudoku> apply(List<Sudoku> selectedCandidates, Random rng) { if (!cached) { buildCache(selectedCandidates.get(0)); } List<Sudoku> mutatedCandidates = new ArrayList<Sudoku>(selectedCandidates.size()); for (Sudoku sudoku : selectedCandidates) { mutatedCandidates.add(mutate(sudoku, rng)); } return mutatedCandidates; } private Sudoku mutate(Sudoku sudoku, Random rng) { Sudoku.Cell[][] newRows = new Sudoku.Cell[Sudoku.SIZE][]; // Mutate each row in turn. for (int i = 0; i < Sudoku.SIZE; i++) { newRows[i] = new Sudoku.Cell[Sudoku.SIZE]; System.arraycopy(sudoku.getRow(i), 0, newRows[i], 0, Sudoku.SIZE); } int mutationCount = Math.abs(mutationCountVariable.nextValue()); while (mutationCount > 0) { int row = rng.nextInt(Sudoku.SIZE); int fromIndex = rng.nextInt(Sudoku.SIZE); int mutationAmount = mutationAmountVariable.nextValue(); int toIndex = (fromIndex + mutationAmount) % Sudoku.SIZE; // Make sure we're not trying to mutate a 'given'. if (!newRows[row][fromIndex].isFixed() && !newRows[row][toIndex].isFixed() // ...or trying to introduce a duplicate of a given value. && (!isIntroducingFixedConflict(sudoku, row, fromIndex, toIndex) || isRemovingFixedConflict(sudoku, row, fromIndex, toIndex))) { // Swap the randomly selected element with the one that is the // specified displacement distance away. Sudoku.Cell temp = newRows[row][fromIndex]; newRows[row][fromIndex] = newRows[row][toIndex]; newRows[row][toIndex] = temp; --mutationCount; } } return new Sudoku(newRows); } private void buildCache(Sudoku sudoku) { for (int row = 0; row < Sudoku.SIZE; row++) { for (int column = 0; column < Sudoku.SIZE; column++) { if (sudoku.isFixed(row, column)) { columnFixedValues[column][sudoku.getValue(row, column) - 1] = true; subGridFixedValues[convertToSubGrid(row, column)][sudoku.getValue(row, column) - 1] = true; } } } cached = true; } /** * Checks whether the proposed mutation would introduce a duplicate of a fixed value * into a column or sub-grid. */ private boolean isIntroducingFixedConflict(Sudoku sudoku, int row, int fromIndex, int toIndex) { return columnFixedValues[fromIndex][sudoku.getValue(row, toIndex) - 1] || columnFixedValues[toIndex][sudoku.getValue(row, fromIndex) - 1] || subGridFixedValues[convertToSubGrid(row, fromIndex)][sudoku.getValue(row, toIndex) - 1] || subGridFixedValues[convertToSubGrid(row, toIndex)][sudoku.getValue(row, fromIndex) - 1]; } /** * Checks whether the proposed mutation would remove a duplicate of a fixed value * from a column or sub-grid. */ private boolean isRemovingFixedConflict(Sudoku sudoku, int row, int fromIndex, int toIndex) { return columnFixedValues[fromIndex][sudoku.getValue(row, fromIndex) - 1] || columnFixedValues[toIndex][sudoku.getValue(row, toIndex) - 1] || subGridFixedValues[convertToSubGrid(row, fromIndex)][sudoku.getValue(row, fromIndex) - 1] || subGridFixedValues[convertToSubGrid(row, toIndex)][sudoku.getValue(row, toIndex) - 1]; } /** * Returns the index of the sub-grid that the specified cells belongs to. * @return A number between 0 (top left) and 8 (bottom right). */ private int convertToSubGrid(int row, int column) { int band = row / 3; int stack = column / 3; return band * 3 + stack; } }