/** * 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.util.*; public class Pentomino { public static final String DEPTH = "mapreduce.pentomino.depth"; public static final String WIDTH = "mapreduce.pentomino.width"; public static final String HEIGHT = "mapreduce.pentomino.height"; public static final String CLASS = "mapreduce.pentomino.class"; /** * This interface just is a marker for what types I expect to get back * as column names. */ protected static interface ColumnName { // NOTHING } /** * Maintain information about a puzzle piece. */ protected static class Piece implements ColumnName { private String name; private boolean [][] shape; private int[] rotations; private boolean flippable; public Piece(String name, String shape, boolean flippable, int[] rotations) { this.name = name; this.rotations = rotations; this.flippable = flippable; StringTokenizer parser = new StringTokenizer(shape, "/"); List<boolean[]> lines = new ArrayList<boolean[]>(); while (parser.hasMoreTokens()) { String token = parser.nextToken(); boolean[] line = new boolean[token.length()]; for(int i=0; i < line.length; ++i) { line[i] = token.charAt(i) == 'x'; } lines.add(line); } this.shape = new boolean[lines.size()][]; for(int i=0 ; i < lines.size(); i++) { this.shape[i] = lines.get(i); } } public String getName() { return name; } public int[] getRotations() { return rotations.clone(); } public boolean getFlippable() { return flippable; } private int doFlip(boolean flip, int x, int max) { if (flip) { return max - x - 1; } else { return x; } } public boolean[][] getShape(boolean flip, int rotate) { boolean [][] result; if (rotate % 2 == 0) { int height = shape.length; int width = shape[0].length; result = new boolean[height][]; boolean flipX = rotate == 2; boolean flipY = flip ^ (rotate == 2); for (int y = 0; y < height; ++y) { result[y] = new boolean[width]; for (int x=0; x < width; ++x) { result[y][x] = shape[doFlip(flipY, y, height)] [doFlip(flipX, x, width)]; } } } else { int height = shape[0].length; int width = shape.length; result = new boolean[height][]; boolean flipX = rotate == 3; boolean flipY = flip ^ (rotate == 1); for (int y = 0; y < height; ++y) { result[y] = new boolean[width]; for (int x=0; x < width; ++x) { result[y][x] = shape[doFlip(flipX, x, width)] [doFlip(flipY, y, height)]; } } } return result; } } /** * A point in the puzzle board. This represents a placement of a piece into * a given point on the board. */ static class Point implements ColumnName { int x; int y; Point(int x, int y) { this.x = x; this.y = y; } } /** * Convert a solution to the puzzle returned by the model into a string * that represents the placement of the pieces onto the board. * @param width the width of the puzzle board * @param height the height of the puzzle board * @param solution the list of column names that were selected in the model * @return a string representation of completed puzzle board */ public static String stringifySolution(int width, int height, List<List<ColumnName>> solution) { String[][] picture = new String[height][width]; StringBuffer result = new StringBuffer(); // for each piece placement... for(List<ColumnName> row: solution) { // go through to find which piece was placed Piece piece = null; for(ColumnName item: row) { if (item instanceof Piece) { piece = (Piece) item; break; } } // for each point where the piece was placed, mark it with the piece name for(ColumnName item: row) { if (item instanceof Point) { Point p = (Point) item; picture[p.y][p.x] = piece.getName(); } } } // put the string together for(int y=0; y < picture.length; ++y) { for (int x=0; x < picture[y].length; ++x) { result.append(picture[y][x]); } result.append("\n"); } return result.toString(); } public enum SolutionCategory {UPPER_LEFT, MID_X, MID_Y, CENTER} /** * Find whether the solution has the x in the upper left quadrant, the * x-midline, the y-midline or in the center. * @param names the solution to check * @return the catagory of the solution */ public SolutionCategory getCategory(List<List<ColumnName>> names) { Piece xPiece = null; // find the "x" piece for(Piece p: pieces) { if ("x".equals(p.name)) { xPiece = p; break; } } // find the row containing the "x" for(List<ColumnName> row: names) { if (row.contains(xPiece)) { // figure out where the "x" is located int low_x = width; int high_x = 0; int low_y = height; int high_y = 0; for(ColumnName col: row) { if (col instanceof Point) { int x = ((Point) col).x; int y = ((Point) col).y; if (x < low_x) { low_x = x; } if (x > high_x) { high_x = x; } if (y < low_y) { low_y = y; } if (y > high_y) { high_y = y; } } } boolean mid_x = (low_x + high_x == width - 1); boolean mid_y = (low_y + high_y == height - 1); if (mid_x && mid_y) { return SolutionCategory.CENTER; } else if (mid_x) { return SolutionCategory.MID_X; } else if (mid_y) { return SolutionCategory.MID_Y; } break; } } return SolutionCategory.UPPER_LEFT; } /** * A solution printer that just writes the solution to stdout. */ private static class SolutionPrinter implements DancingLinks.SolutionAcceptor<ColumnName> { int width; int height; public SolutionPrinter(int width, int height) { this.width = width; this.height = height; } public void solution(List<List<ColumnName>> names) { System.out.println(stringifySolution(width, height, names)); } } protected int width; protected int height; protected List<Piece> pieces = new ArrayList<Piece>(); /** * Is the piece fixed under rotation? */ protected static final int [] oneRotation = new int[]{0}; /** * Is the piece identical if rotated 180 degrees? */ protected static final int [] twoRotations = new int[]{0,1}; /** * Are all 4 rotations unique? */ protected static final int [] fourRotations = new int[]{0,1,2,3}; /** * Fill in the pieces list. */ protected void initializePieces() { pieces.add(new Piece("x", " x /xxx/ x ", false, oneRotation)); pieces.add(new Piece("v", "x /x /xxx", false, fourRotations)); pieces.add(new Piece("t", "xxx/ x / x ", false, fourRotations)); pieces.add(new Piece("w", " x/ xx/xx ", false, fourRotations)); pieces.add(new Piece("u", "x x/xxx", false, fourRotations)); pieces.add(new Piece("i", "xxxxx", false, twoRotations)); pieces.add(new Piece("f", " xx/xx / x ", true, fourRotations)); pieces.add(new Piece("p", "xx/xx/x ", true, fourRotations)); pieces.add(new Piece("z", "xx / x / xx", true, twoRotations)); pieces.add(new Piece("n", "xx / xxx", true, fourRotations)); pieces.add(new Piece("y", " x /xxxx", true, fourRotations)); pieces.add(new Piece("l", " x/xxxx", true, fourRotations)); } /** * Is the middle of piece on the upper/left side of the board with * a given offset and size of the piece? This only checks in one * dimension. * @param offset the offset of the piece * @param shapeSize the size of the piece * @param board the size of the board * @return is it in the upper/left? */ private static boolean isSide(int offset, int shapeSize, int board) { return 2*offset + shapeSize <= board; } /** * For a given piece, generate all of the potential placements and add them * as rows to the model. * @param dancer the problem model * @param piece the piece we are trying to place * @param width the width of the board * @param height the height of the board * @param flip is the piece flipped over? * @param row a workspace the length of the each row in the table * @param upperLeft is the piece constrained to the upper left of the board? * this is used on a single piece to eliminate most of the trivial * roations of the solution. */ private static void generateRows(DancingLinks dancer, Piece piece, int width, int height, boolean flip, boolean[] row, boolean upperLeft) { // for each rotation int[] rotations = piece.getRotations(); for(int rotIndex = 0; rotIndex < rotations.length; ++rotIndex) { // get the shape boolean[][] shape = piece.getShape(flip, rotations[rotIndex]); // find all of the valid offsets for(int x=0; x < width; ++x) { for(int y=0; y < height; ++y) { if (y + shape.length <= height && x + shape[0].length <= width && (!upperLeft || (isSide(x, shape[0].length, width) && isSide(y, shape.length, height)))) { // clear the columns related to the points on the board for(int idx=0; idx < width * height; ++idx) { row[idx] = false; } // mark the shape for(int subY=0; subY < shape.length; ++subY) { for(int subX=0; subX < shape[0].length; ++subX) { row[(y + subY) * width + x + subX] = shape[subY][subX]; } } dancer.addRow(row); } } } } } private DancingLinks<ColumnName> dancer = new DancingLinks<ColumnName>(); private DancingLinks.SolutionAcceptor<ColumnName> printer; { initializePieces(); } /** * Create the model for a given pentomino set of pieces and board size. * @param width the width of the board in squares * @param height the height of the board in squares */ public Pentomino(int width, int height) { initialize(width, height); } /** * Create the object without initialization. */ public Pentomino() { } void initialize(int width, int height) { this.width = width; this.height = height; for(int y=0; y < height; ++y) { for(int x=0; x < width; ++x) { dancer.addColumn(new Point(x,y)); } } int pieceBase = dancer.getNumberColumns(); for(Piece p: pieces) { dancer.addColumn(p); } boolean[] row = new boolean[dancer.getNumberColumns()]; for(int idx = 0; idx < pieces.size(); ++idx) { Piece piece = pieces.get(idx); row[idx + pieceBase] = true; generateRows(dancer, piece, width, height, false, row, idx == 0); if (piece.getFlippable()) { generateRows(dancer, piece, width, height, true, row, idx == 0); } row[idx + pieceBase] = false; } printer = new SolutionPrinter(width, height); } /** * Generate a list of prefixes to a given depth * @param depth the length of each prefix * @return a list of arrays of ints, which are potential prefixes */ public List<int[]> getSplits(int depth) { return dancer.split(depth); } /** * Find all of the solutions that start with the given prefix. The printer * is given each solution as it is found. * @param split a list of row indexes that should be choosen for each row * in order * @return the number of solutions found */ public int solve(int[] split) { return dancer.solve(split, printer); } /** * Find all of the solutions to the puzzle. * @return the number of solutions found */ public int solve() { return dancer.solve(printer); } /** * Set the printer for the puzzle. * @param printer A call-back object that is given each solution as it is * found. */ public void setPrinter(DancingLinks.SolutionAcceptor<ColumnName> printer) { this.printer = printer; } /** * Solve the 6x10 pentomino puzzle. */ public static void main(String[] args) { int width = 6; int height = 10; Pentomino model = new Pentomino(width, height); List splits = model.getSplits(2); for(Iterator splitItr=splits.iterator(); splitItr.hasNext(); ) { int[] choices = (int[]) splitItr.next(); System.out.print("split:"); for(int i=0; i < choices.length; ++i) { System.out.print(" " + choices[i]); } System.out.println(); System.out.println(model.solve(choices) + " solutions found."); } } }