blob: 692048c0e407afc0da82bb4801a9ae0527d2d1d9 [file] [log] [blame]
/**
* 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();
}
}
}