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 } |