/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.examples.sudoku; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import kodkod.instance.Tuple; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; import kodkod.instance.Universe; /** * A utility for parsing various String specifications of Sudoku puzzles. * @author Emina Torlak */ public final class SudokuParser { private SudokuParser() {} private static String[] split(String puzzle) { final String[] parsed = puzzle.split("\\s+"); return (parsed.length>1) ? parsed : puzzle.replaceAll("(\\d)", "$1 ").split(" "); } /** * Returns the representation of the clues encoded in the given array as * an NxNxN tuple set drawn from the given universe. N is assumed * to be a perfect square, and the universe is assumed to consist of Integer objects in * the range [1..N]. The array is assumed to consist of N*N numbers, drawn * from the set 0..N, inclusive, where a consecutive sequence of N * numbers represents one row of an NxN Sudoku grid. * Zeros stand for blank entries. * @requires some r: int | puzzle.length = r * r * r * r * @requires universe.atoms[int] = {i: Integer | 1 <= i.intValue() <= puzzle.length} * @return a tupleset representation of the clues in the puzzle specified by the given array. */ public static final TupleSet parse(String[] puzzle, Universe univ) { final int n = (int)StrictMath.sqrt(puzzle.length); final int r = (int)StrictMath.sqrt(n); if (puzzle.length!=r*r*r*r) throw new IllegalArgumentException("Not a valid puzzle spec: expected " + (r*r*r*r) + " numbers but found " + puzzle.length); final TupleFactory f = univ.factory(); final TupleSet givens = f.noneOf(3); for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { try { final int digit = Integer.parseInt(puzzle[i*n+j]); if (digit>0 && digit<=n) { final Tuple t = f.tuple(i+1, j+1, digit); givens.add(t); } else if (digit>n) { throw new IllegalArgumentException("Not a valid puzzle spec: expected numbers from [0, " + n+"] but found "+digit); } } catch (NumberFormatException nfe) { throw new IllegalArgumentException("Not a valid puzzle spec: expected numbers from [0, " + n+"] but found "+puzzle[i*n+j]); } } } return givens; } /** * Returns the representation of the clues encoded in the given array as * an NxNxN tuple set drawn from a freshly constructed universe. N is assumed * to be a perfect square, and the universe consist of Integer objects in * the range [1..N]. The array is assumed to consist of N*N numbers, drawn * from the set 0..N, inclusive, where a consecutive sequence of N * numbers represents one row of an NxN Sudoku grid. * Zeros stand for blank entries. * @requires some r: int | puzzle.length = r * r * r * r * @return a tupleset representation of the clues in the puzzle specified by the given array. */ public static final TupleSet parse(String[] puzzle) { final Integer[] atoms = new Integer[(int)StrictMath.sqrt(puzzle.length)]; for(int i = 0; i < atoms.length; i++) { atoms[i] = i+1; } return parse(puzzle, new Universe((Object[])atoms)); } /** * Parses the given puzzle string and returns the representation of * the encoded clues as an NxNxN tuple set drawn from a freshly constructed universe. * N is assumed to be a perfect square, and the universe consists of Integer objects * in the range [1..N]. If N>9, this method assumes that the puzzle * string consists of N*N space-separated numbers, drawn * from the set 0..N, inclusive, where a consecutive sequence of N * space-separated numbers represents one row of an NxN Sudoku grid. * Zeros stand for blank entries. If N<=9, then the spaces may be omitted. * @requires some r: [2..) | (puzzle.split("\\s+").length() = r * r * r * r) || (r<=3 && puzzle.length = r * r * r * r) * @return a tupleset representation of the clues in the puzzle specified by the given string. */ public static final TupleSet parse(String puzzle) { final String[] parsed = split(puzzle); final Integer[] atoms = new Integer[(int)StrictMath.sqrt(parsed.length)]; for(int i = 0; i < atoms.length; i++) { atoms[i] = i+1; } return parse(parsed, new Universe((Object[])atoms)); } /** * Parses the given puzzle string and returns the representation of * the encoded clues as an NxNxN tuple set drawn from the given universe. N is assumed * to be a perfect square, and the universe is assumed to consist of Integer objects in * the range [1..N]. If N>9, the puzzle string is assumed to consist of N*N space-separated numbers, drawn * from the set 0..N, inclusive, where a consecutive sequence of N * space-separated numbers represents one row of an NxN Sudoku grid. * Zeros stand for blank entries. If N<=9, then the spaces may be omitted. * @requires some r: [2..) | (puzzle.split("\\s+").length() = r * r * r * r) || (r<=3 && puzzle.length = r * r * r * r) * @requires universe.atoms[int] = {i: Integer | 1 <= i.intValue() <= max(puzzle.split("\\s+").length(), puzzle.length())} * @return a tupleset representation of the clues in the puzzle specified by the given string. */ public static final TupleSet parse(String puzzle, Universe univ) { return parse(split(puzzle), univ); } /** * Returns a string representation of the given puzzle. * @requires some r: int | puzzle.universe.atoms[int] = { i: Integer | 1 <= i.intValue() <= r*r } * @requires puzzle.arity = 3 * @return a string representation of the given puzzle */ public static final String toString(TupleSet puzzle) { final StringBuilder str = new StringBuilder(); final int n = puzzle.universe().size(); final String sep = (n>9) ? " " : ""; Iterator<Tuple> itr = puzzle.iterator(); if (!itr.hasNext()) { str.append(0); for(int i = 1, max = n*n; i < max; i++) { str.append(sep+0); } return str.toString(); } int last = 0; Tuple tuple = itr.next(); if ((Integer)tuple.atom(0)==1 && (Integer)tuple.atom(1)==1) { str.append(tuple.atom(2)); } else { str.append(0); itr = puzzle.iterator(); } while(itr.hasNext()) { tuple = itr.next(); final int current = n*((Integer)tuple.atom(0)-1) + ((Integer)tuple.atom(1)-1); for(int i = last+1; i < current; i++) { str.append(sep+0); } str.append(sep+tuple.atom(2)); last = current; } for(int i = last+1, max = n*n; i < max; i++) { str.append(sep+0); } return str.toString(); } private static void appendDivider(StringBuilder str, int r) { final String len = (r<=3) ? "--" : "---"; for(int i = 0; i < r; i++) { str.append("+-"); for(int j = 0; j < r; j++) { str.append(len); } } str.append("+\n"); } /** * Returns a pretty-printed string of the given sudoku solution. * @requires solution is a valid sudoku solution * @requires some r: int | solution.universe = { i: Integer | 1 <= i.intValue() <= r*r } * @return a pretty-printed string of the given sudoku solution */ public static final String prettyPrint(TupleSet solution) { final StringBuilder str = new StringBuilder(); final int n = solution.universe().size(); final int r = (int)Math.sqrt(n); appendDivider(str, r); final Iterator<Tuple> psol = solution.iterator(); for(int i = 1; i <= n; i++) { str.append("| "); for(int j = 0; j < r; j++) { for(int k = 0; k < r; k++) { final int atom = (Integer)psol.next().atom(2); if (atom<10&&r>3) str.append(" "); str.append(atom); str.append(" "); } str.append("| "); } str.append("\n"); if (i%r==0) appendDivider(str, r); } return str.toString(); } /** * Returns a map that maps each option in the given argument array to its value, * or null if the option has no value. * Assumes that all options are of the form "-opt=val" or "-opt". * @return a map that maps each option in the given argument array to its value, * or null if the option has no value. */ static Map<String, String> options(String[] args) { final Map<String,String> opts = new LinkedHashMap<String,String>(); for(String arg: args) { if (arg.startsWith("-")) { String[] opt = arg.split("="); switch(opt.length) { case 1 : opts.put(opt[0], null); break; case 2 : opts.put(opt[0], opt[1]); break; default : throw new IllegalArgumentException("Unrecognized option format: " + arg); } } } return opts; } }