/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.hadoop.examples.dancing; import java.io.*; import java.util.*; /** * This class uses the dancing links algorithm from Knuth to solve sudoku * puzzles. It has solved 42x42 puzzles in 1.02 seconds. */ public class Sudoku { /** * The preset values in the board * board[y][x] is the value at x,y with -1 = any */ private int[][] board; /** * The size of the board */ private int size; /** * The size of the sub-squares in cells across */ private int squareXSize; /** * The size of the sub-squares in celss up and down */ private int squareYSize; /** * This interface is a marker class for the columns created for the * Sudoku solver. */ protected static interface ColumnName { // NOTHING } /** * A string containing a representation of the solution. * @param size the size of the board * @param solution a list of list of column names * @return a string of the solution matrix */ static String stringifySolution(int size, List<List<ColumnName>> solution) { int[][] picture = new int[size][size]; StringBuffer result = new StringBuffer(); // go through the rows selected in the model and build a picture of the // solution. for(List<ColumnName> row: solution) { int x = -1; int y = -1; int num = -1; for(ColumnName item: row) { if (item instanceof ColumnConstraint) { x = ((ColumnConstraint) item).column; num = ((ColumnConstraint) item).num; } else if (item instanceof RowConstraint) { y = ((RowConstraint) item).row; } } picture[y][x] = num; } // build the string for(int y=0; y < size; ++y) { for (int x=0; x < size; ++x) { result.append(picture[y][x]); result.append(" "); } result.append("\n"); } return result.toString(); } /** * An acceptor to get the solutions to the puzzle as they are generated and * print them to the console. */ private static class SolutionPrinter implements DancingLinks.SolutionAcceptor<ColumnName> { int size; public SolutionPrinter(int size) { this.size = size; } /** * A debugging aid that just prints the raw information about the * dancing link columns that were selected for each row. * @param solution a list of list of column names */ void rawWrite(List solution) { for (Iterator itr=solution.iterator(); itr.hasNext(); ) { Iterator subitr = ((List) itr.next()).iterator(); while (subitr.hasNext()) { System.out.print(subitr.next().toString() + " "); } System.out.println(); } } public void solution(List<List<ColumnName>> names) { System.out.println(stringifySolution(size, names)); } } /** * Set up a puzzle board to the given size. * Boards may be asymmetric, but the squares will always be divided to be * more cells wide than they are tall. For example, a 6x6 puzzle will make * sub-squares that are 3x2 (3 cells wide, 2 cells tall). Clearly that means * the board is made up of 2x3 sub-squares. * @param stream The input stream to read the data from */ public Sudoku(InputStream stream) throws IOException { BufferedReader file = new BufferedReader(new InputStreamReader(stream)); String line = file.readLine(); List<int[]> result = new ArrayList<int[]>(); while (line != null) { StringTokenizer tokenizer = new StringTokenizer(line); int size = tokenizer.countTokens(); int[] col = new int[size]; int y = 0; while(tokenizer.hasMoreElements()) { String word = tokenizer.nextToken(); if ("?".equals(word)) { col[y] = - 1; } else { col[y] = Integer.parseInt(word); } y += 1; } result.add(col); line = file.readLine(); } size = result.size(); board = result.toArray(new int [size][]); squareYSize = (int) Math.sqrt(size); squareXSize = size / squareYSize; file.close(); } /** * A constraint that each number can appear just once in a column. */ static private class ColumnConstraint implements ColumnName { ColumnConstraint(int num, int column) { this.num = num; this.column = column; } int num; int column; public String toString() { return num + " in column " + column; } } /** * A constraint that each number can appear just once in a row. */ static private class RowConstraint implements ColumnName { RowConstraint(int num, int row) { this.num = num; this.row = row; } int num; int row; public String toString() { return num + " in row " + row; } } /** * A constraint that each number can appear just once in a square. */ static private class SquareConstraint implements ColumnName { SquareConstraint(int num, int x, int y) { this.num = num; this.x = x; this.y = y; } int num; int x; int y; public String toString() { return num + " in square " + x + "," + y; } } /** * A constraint that each cell can only be used once. */ static private class CellConstraint implements ColumnName { CellConstraint(int x, int y) { this.x = x; this.y = y; } int x; int y; public String toString() { return "cell " + x + "," + y; } } /** * Create a row that places num in cell x, y. * @param rowValues a scratch pad to mark the bits needed * @param x the horizontal offset of the cell * @param y the vertical offset of the cell * @param num the number to place * @return a bitvector of the columns selected */ private boolean[] generateRow(boolean[] rowValues, int x, int y, int num) { // clear the scratch array for(int i=0; i < rowValues.length; ++i) { rowValues[i] = false; } // find the square coordinates int xBox = x / squareXSize; int yBox = y / squareYSize; // mark the column rowValues[x*size + num - 1] = true; // mark the row rowValues[size*size + y*size + num - 1] = true; // mark the square rowValues[2*size*size + (xBox*squareXSize + yBox)*size + num - 1] = true; // mark the cell rowValues[3*size*size + size*x + y] = true; return rowValues; } private DancingLinks<ColumnName> makeModel() { DancingLinks<ColumnName> model = new DancingLinks<ColumnName>(); // create all of the columns constraints for(int x=0; x < size; ++x) { for(int num=1; num <= size; ++num) { model.addColumn(new ColumnConstraint(num, x)); } } // create all of the row constraints for(int y=0; y < size; ++y) { for(int num=1; num <= size; ++num) { model.addColumn(new RowConstraint(num, y)); } } // create the square constraints for(int x=0; x < squareYSize; ++x) { for(int y=0; y < squareXSize; ++y) { for(int num=1; num <= size; ++num) { model.addColumn(new SquareConstraint(num, x, y)); } } } // create the cell constraints for(int x=0; x < size; ++x) { for(int y=0; y < size; ++y) { model.addColumn(new CellConstraint(x, y)); } } boolean[] rowValues = new boolean[size*size*4]; for(int x=0; x < size; ++x) { for(int y=0; y < size; ++y) { if (board[y][x] == -1) { // try each possible value in the cell for(int num=1; num <= size; ++num) { model.addRow(generateRow(rowValues, x, y, num)); } } else { // put the given cell in place model.addRow(generateRow(rowValues, x, y, board[y][x])); } } } return model; } public void solve() { DancingLinks<ColumnName> model = makeModel(); int results = model.solve(new SolutionPrinter(size)); System.out.println("Found " + results + " solutions"); } /** * Solves a set of sudoku puzzles. * @param args a list of puzzle filenames to solve */ public static void main(String[] args) throws IOException { if (args.length == 0) { System.out.println("Include a puzzle on the command line."); } for(int i=0; i < args.length; ++i) { Sudoku problem = new Sudoku(new FileInputStream(args[i])); System.out.println("Solving " + args[i]); problem.solve(); } } }