| 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 26 * POSSIBILITY OF SUCH DAMAGE. |
24 * POSSIBILITY OF SUCH DAMAGE. |
| 27 * |
|
| 28 */ |
25 */ |
| 29 |
26 |
| 30 package de.uapcore.sigred.doc.base; |
27 package de.uapcore.sudoku; |
| 31 |
28 |
| 32 import de.uapcore.sigred.doc.Resources; |
29 import java.util.ArrayList; |
| 33 import de.uapcore.sigrapi.impl.Digraph; |
30 import java.util.List; |
| 34 import de.uapcore.sigrapi.impl.Graph; |
31 |
| 35 import de.uapcore.sigrapi.IGraph; |
32 /** |
| 36 import java.io.IOException; |
33 * Implements the backtracking algorithm for solving the Sudoku. |
| 37 import java.io.InputStream; |
34 */ |
| 38 import java.io.OutputStream; |
35 public final class Solver { |
| 39 import java.util.concurrent.atomic.AtomicBoolean; |
36 |
| 40 import java.util.concurrent.atomic.AtomicReference; |
37 public static final int VERSION = 0x1000; |
| 41 import org.apache.xerces.impl.Constants; |
38 |
| 42 import org.dom4j.Document; |
39 private Integer fillInCandidate(Field f, List<Integer>[][] candidates, int x, int y) { |
| 43 import org.dom4j.DocumentException; |
40 Integer c = candidates[x][y].remove(0); |
| 44 import org.dom4j.DocumentHelper; |
41 f.setCellValue(x, y, c); |
| 45 import org.dom4j.Element; |
42 f.setCellModified(x, y, true); |
| 46 import org.dom4j.Namespace; |
43 for (int i = 0 ; i < 9 ; i++) { |
| 47 import org.dom4j.QName; |
44 candidates[x][i].remove(c); |
| 48 import org.dom4j.io.OutputFormat; |
45 candidates[i][y].remove(c); |
| 49 import org.dom4j.io.SAXReader; |
46 } |
| 50 import org.dom4j.io.XMLWriter; |
47 for (int i = 0 ; i < 3 ; i++) { |
| 51 import org.xml.sax.ErrorHandler; |
48 for (int j = 0 ; j < 3 ; j++) { |
| 52 import org.xml.sax.SAXException; |
49 candidates[x-x%3+i][y-y%3+j].remove(c); |
| 53 import org.xml.sax.SAXParseException; |
50 } |
| 54 |
51 } |
| 55 public abstract class AbstractGraphDocument<T extends IGraph> |
52 return c; |
| 56 extends FileBackedDocument { |
53 } |
| 57 |
54 |
| 58 protected static final Namespace NAMESPACE = Namespace.get("sigred", |
55 private void makeBackup(List<Integer>[][] candidates, List<Integer>[][] candidatesBackup) { |
| 59 "http://develop.uap-core.de/sigred/"); |
56 for (int x = 0 ; x < 9 ; x++) { |
| 60 |
57 for (int y = 0 ; y < 9 ; y++) { |
| 61 private static final |
58 candidatesBackup[x][y] = new ArrayList<>(9); |
| 62 QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE); |
59 candidatesBackup[x][y].addAll(candidates[x][y]); |
| 63 private static final |
60 } |
| 64 QName TAG_GRAPH = QName.get("graph", NAMESPACE); |
61 } |
| 65 private static final |
62 } |
| 66 QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE); |
63 |
| 67 private static final |
64 private void makeBackup(Field f, int[][] fieldBackup) { |
| 68 QName TAG_METADATA = QName.get("metadata", NAMESPACE); |
65 for (int x = 0 ; x < 9 ; x++) { |
| 69 |
66 for (int y = 0 ; y < 9 ; y++) { |
| 70 protected final T graph; |
67 fieldBackup[x][y] = f.getCellValue(x, y); |
| 71 |
68 } |
| 72 private final GraphDocumentMetadata metadata; |
69 } |
| 73 |
70 } |
| 74 public AbstractGraphDocument(Class<T> graphType) { |
71 |
| 75 T g; |
72 private void restoreBackup(Field f, int[][] fieldBackup) { |
| 76 try { |
73 for (int x = 0 ; x < 9 ; x++) { |
| 77 g = graphType.newInstance(); |
74 for (int y = 0 ; y < 9 ; y++) { |
| 78 } catch (ReflectiveOperationException e) { |
75 f.setCellValue(x, y, fieldBackup[x][y]); |
| 79 assert false; |
76 } |
| 80 g = null; // for the compiler |
77 } |
| 81 } |
78 } |
| 82 graph = g; |
79 |
| 83 metadata = new GraphDocumentMetadata(); |
80 private void restoreBackup(List<Integer>[][] candidates, List<Integer>[][] candidatesBackup) { |
| 84 } |
81 for (int x = 0 ; x < 9 ; x++) { |
| 85 |
82 for (int y = 0 ; y < 9 ; y++) { |
| 86 public T getGraph() { |
83 candidates[x][y].clear(); |
| 87 return graph; |
84 candidates[x][y].addAll(candidatesBackup[x][y]); |
| 88 } |
85 } |
| 89 |
86 } |
| 90 public GraphDocumentMetadata getMetadata() { |
87 } |
| 91 return metadata; |
88 |
| 92 } |
89 private boolean solve(Field f, List<Integer>[][] candidates) { |
| 93 |
90 |
| 94 protected abstract void writeGraph(Element rootNode) throws IOException; |
91 // Make backup |
| 95 protected abstract void readGraph(Element rootNode) throws IOException; |
92 List<Integer>[][] candidatesBackup = new List[9][9]; |
| 96 |
93 int[][] fieldBackup = new int[9][9]; |
| 97 @Override |
94 makeBackup(candidates, candidatesBackup); |
| 98 public void writeTo(OutputStream out) throws IOException { |
95 makeBackup(f, fieldBackup); |
| 99 Document doc = DocumentHelper.createDocument(); |
96 |
| 100 |
97 // Fill in distinct solutions |
| 101 Element rootNode = doc.addElement(TAG_GRAPHDOC); |
98 boolean fillDistinct; |
| 102 |
99 do { |
| 103 Element metadataNode = rootNode.addElement(TAG_METADATA); |
100 fillDistinct = false; |
| 104 |
101 for (int x = 0 ; x < 9 ; x++) { |
| 105 metadata.write(metadataNode); |
102 for (int y = 0 ; y < 9 ; y++) { |
| 106 |
103 if (f.isCellEmpty(x, y) && candidates[x][y].size() == 1) { |
| 107 if (graph instanceof Graph) { |
104 fillInCandidate(f, candidates, x, y); |
| 108 writeGraph(rootNode.addElement(TAG_GRAPH)); |
105 fillDistinct = true; |
| 109 } else if (graph instanceof Digraph) { |
106 } |
| 110 writeGraph(rootNode.addElement(TAG_DIGRAPH)); |
107 } |
| 111 } else { |
108 } |
| 112 throw new IOException("unsupported graph type"); |
109 } while (fillDistinct); |
| 113 } |
110 |
| 114 |
111 // Try out remaining candidates |
| 115 XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint()); |
112 for (int x = 0 ; x < 9 ; x++) { |
| 116 writer.write(doc); |
113 for (int y = 0 ; y < 9 ; y++) { |
| 117 writer.flush(); |
114 if (f.isCellEmpty(x, y)) { |
| 118 } |
115 while (candidates[x][y].size() > 0) { |
| 119 |
116 List<Integer>[][] cb = new List[9][9]; |
| 120 @Override |
117 makeBackup(candidates, cb); |
| 121 public void readFrom(InputStream in) throws IOException { |
118 Integer c = fillInCandidate(f, candidates, x, y); |
| 122 try { |
119 if (solve(f, candidates)) { |
| 123 SAXReader reader = new SAXReader(true); |
120 break; |
| 124 reader.setStripWhitespaceText(true); |
121 } else { |
| 125 |
122 f.clearCellValue(x, y); |
| 126 reader.setFeature(Constants.XERCES_FEATURE_PREFIX+ |
123 restoreBackup(candidates, cb); |
| 127 Constants.SCHEMA_VALIDATION_FEATURE, true); |
124 // Remove current candidate anyway |
| 128 reader.setProperty(Constants.XERCES_PROPERTY_PREFIX + |
125 candidates[x][y].remove(c); |
| 129 Constants.SCHEMA_LOCATION, String.format("%s %s", |
126 } |
| 130 NAMESPACE.getURI(), Resources.class.getResource( |
127 } |
| 131 "graph-document.xsd").toExternalForm())); |
128 } |
| 132 |
129 if (f.isCellEmpty(x, y)) { |
| 133 final AtomicBoolean passed = new AtomicBoolean(true); |
130 restoreBackup(f, fieldBackup); |
| 134 final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>(); |
131 restoreBackup(candidates, candidatesBackup); |
| 135 // TODO: we should do more detailed error handling here |
132 return false; |
| 136 reader.setErrorHandler(new ErrorHandler() { |
133 } |
| 137 @Override |
134 } |
| 138 public void warning(SAXParseException exception) throws SAXException { |
135 } |
| 139 } |
136 |
| 140 |
137 return true; |
| 141 @Override |
138 } |
| 142 public void error(SAXParseException exception) throws SAXException { |
139 |
| 143 xmlerror.set(exception); |
140 /** |
| 144 passed.set(false); |
141 * Attempts to solve the given Sudoku field. |
| 145 } |
142 * |
| 146 |
143 * The solution, if any, is directly entered into the field. |
| 147 @Override |
144 * All solved fields will be in modified state. |
| 148 public void fatalError(SAXParseException exception) throws SAXException { |
145 * The already given fields are left untouched. |
| 149 xmlerror.set(exception); |
146 * |
| 150 passed.set(false); |
147 * @param f the field to solve |
| 151 } |
148 * @return true if a solution could be found, false if the field is unsolvable |
| 152 |
149 */ |
| 153 }); |
150 public boolean solve(Field f) { |
| 154 Document doc = reader.read(in); |
151 |
| 155 if (!passed.get()) { |
152 // Calculate initial candidates |
| 156 // TODO: provide details (maybe via separate error object?) |
153 List<Integer>[][] candidates = new List[9][9]; |
| 157 throw xmlerror.get(); |
154 for (int x = 0 ; x < 9 ; x++) { |
| 158 } |
155 for (int y = 0 ; y < 9 ; y++) { |
| 159 |
156 candidates[x][y] = new ArrayList<>(9); |
| 160 doc.normalize(); |
157 if (f.getCellValue(x, y) == 0) { |
| 161 |
158 // All numbers are candidates |
| 162 Element root = doc.getRootElement(); |
159 for (int c = 1 ; c <= 9 ; c++) { |
| 163 metadata.read(root.element(TAG_METADATA)); |
160 candidates[x][y].add(c); |
| 164 |
161 } |
| 165 if (graph instanceof Graph) { |
162 // Remove row duplicates |
| 166 readGraph(root.element(TAG_GRAPH)); |
163 int[] line = f.getRow(y); |
| 167 } else if (graph instanceof Digraph) { |
164 for (Integer c : line) { |
| 168 readGraph(root.element(TAG_DIGRAPH)); |
165 candidates[x][y].remove(c); |
| 169 } else { |
166 } |
| 170 throw new IOException("unsupported graph type"); |
167 // Remove column duplicates |
| 171 } |
168 line = f.getColumn(x); |
| 172 } catch (DocumentException | SAXException ex) { |
169 for (Integer c : line) { |
| 173 throw new IOException(ex); |
170 candidates[x][y].remove(c); |
| 174 } |
171 } |
| |
172 // Remove square duplicates |
| |
173 int[][] square = f.getSquare(x/3, y/3); |
| |
174 for (int[] sq : square) { |
| |
175 for (Integer c : sq) { |
| |
176 candidates[x][y].remove(c); |
| |
177 } |
| |
178 } |
| |
179 } |
| |
180 } |
| |
181 } |
| |
182 |
| |
183 // Backtrack |
| |
184 return solve(f, candidates); |
| |
185 } |
| |
186 |
| |
187 /** |
| |
188 * Performs a fast check whether any field violates the Sudoku rules. |
| |
189 * |
| |
190 * @param f the field to check |
| |
191 * @return true, if the check succeeds, false otherwise |
| |
192 */ |
| |
193 public boolean check(Field f) { |
| |
194 int[] line; |
| |
195 for (int i = 0 ; i < 9 ; i++) { |
| |
196 line = f.getRow(i); |
| |
197 if (!valid(line)) { |
| |
198 return false; |
| |
199 } |
| |
200 line = f.getColumn(i); |
| |
201 if (!valid(line)) { |
| |
202 return false; |
| |
203 } |
| |
204 } |
| |
205 |
| |
206 int[][] square; |
| |
207 for (int x = 0 ; x < 3 ; x++) { |
| |
208 for (int y = 0 ; y < 3 ; y++) { |
| |
209 square = f.getSquare(x, y); |
| |
210 if (!valid(square)) { |
| |
211 return false; |
| |
212 } |
| |
213 } |
| |
214 } |
| |
215 |
| |
216 return true; |
| |
217 } |
| |
218 |
| |
219 private boolean valid(int[] line) { |
| |
220 int[] numbers; |
| |
221 numbers = new int[9]; |
| |
222 for (int i = 0 ; i < 9 ; i++) { |
| |
223 int l = line[i]-1; |
| |
224 if (l >= 0) { |
| |
225 if ((++numbers[l]) > 1) { |
| |
226 return false; |
| |
227 } |
| |
228 } |
| |
229 } |
| |
230 |
| |
231 return true; |
| |
232 } |
| |
233 |
| |
234 private boolean valid(int[][] square) { |
| |
235 int[] line = new int[9]; |
| |
236 for (int x = 0 ; x < 3 ; x++) { |
| |
237 System.arraycopy(square[x], 0, line, 3*x, 3); |
| |
238 } |
| |
239 return valid(line); |
| 175 } |
240 } |
| 176 } |
241 } |