143 lines
5.0 KiB
Java
143 lines
5.0 KiB
Java
import java.util.*;
|
|
import static java.util.Arrays.*;
|
|
import static java.util.stream.Collectors.toList;
|
|
|
|
public class NonogramSolver {
|
|
|
|
static String[] p1 = {"C BA CB BB F AE F A B", "AB CA AE GA E C D C"};
|
|
|
|
static String[] p2 = {"F CAC ACAC CN AAA AABB EBB EAA ECCC HCCC", "D D AE "
|
|
+ "CD AE A DA BBB CC AAB BAA AAB DA AAB AAA BAB AAA CD BBA DA"};
|
|
|
|
static String[] p3 = {"CA BDA ACC BD CCAC CBBAC BBBBB BAABAA ABAD AABB BBH "
|
|
+ "BBBD ABBAAA CCEA AACAAB BCACC ACBH DCH ADBE ADBB DBE ECE DAA DB CC",
|
|
"BC CAC CBAB BDD CDBDE BEBDF ADCDFA DCCFB DBCFC ABDBA BBF AAF BADB DBF "
|
|
+ "AAAAD BDG CEF CBDB BBB FC"};
|
|
|
|
static String[] p4 = {"E BCB BEA BH BEK AABAF ABAC BAA BFB OD JH BADCF Q Q "
|
|
+ "R AN AAN EI H G", "E CB BAB AAA AAA AC BB ACC ACCA AGB AIA AJ AJ "
|
|
+ "ACE AH BAF CAG DAG FAH FJ GJ ADK ABK BL CM"};
|
|
|
|
public static void main(String[] args) {
|
|
for (String[] puzzleData : new String[][]{p1, p2, p3, p4})
|
|
newPuzzle(puzzleData);
|
|
}
|
|
|
|
static void newPuzzle(String[] data) {
|
|
String[] rowData = data[0].split("\\s");
|
|
String[] colData = data[1].split("\\s");
|
|
|
|
List<List<BitSet>> cols, rows;
|
|
rows = getCandidates(rowData, colData.length);
|
|
cols = getCandidates(colData, rowData.length);
|
|
|
|
int numChanged;
|
|
do {
|
|
numChanged = reduceMutual(cols, rows);
|
|
if (numChanged == -1) {
|
|
System.out.println("No solution");
|
|
return;
|
|
}
|
|
} while (numChanged > 0);
|
|
|
|
for (List<BitSet> row : rows) {
|
|
for (int i = 0; i < cols.size(); i++)
|
|
System.out.print(row.get(0).get(i) ? "# " : ". ");
|
|
System.out.println();
|
|
}
|
|
System.out.println();
|
|
}
|
|
|
|
// collect all possible solutions for the given clues
|
|
static List<List<BitSet>> getCandidates(String[] data, int len) {
|
|
List<List<BitSet>> result = new ArrayList<>();
|
|
|
|
for (String s : data) {
|
|
List<BitSet> lst = new LinkedList<>();
|
|
|
|
int sumChars = s.chars().map(c -> c - 'A' + 1).sum();
|
|
List<String> prep = stream(s.split(""))
|
|
.map(x -> repeat(x.charAt(0) - 'A' + 1, "1")).collect(toList());
|
|
|
|
for (String r : genSequence(prep, len - sumChars + 1)) {
|
|
char[] bits = r.substring(1).toCharArray();
|
|
BitSet bitset = new BitSet(bits.length);
|
|
for (int i = 0; i < bits.length; i++)
|
|
bitset.set(i, bits[i] == '1');
|
|
lst.add(bitset);
|
|
}
|
|
result.add(lst);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// permutation generator, translated from Python via D
|
|
static List<String> genSequence(List<String> ones, int numZeros) {
|
|
if (ones.isEmpty())
|
|
return asList(repeat(numZeros, "0"));
|
|
|
|
List<String> result = new ArrayList<>();
|
|
for (int x = 1; x < numZeros - ones.size() + 2; x++) {
|
|
List<String> skipOne = ones.stream().skip(1).collect(toList());
|
|
for (String tail : genSequence(skipOne, numZeros - x))
|
|
result.add(repeat(x, "0") + ones.get(0) + tail);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static String repeat(int n, String s) {
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < n; i++)
|
|
sb.append(s);
|
|
return sb.toString();
|
|
}
|
|
|
|
/* If all the candidates for a row have a value in common for a certain cell,
|
|
then it's the only possible outcome, and all the candidates from the
|
|
corresponding column need to have that value for that cell too. The ones
|
|
that don't, are removed. The same for all columns. It goes back and forth,
|
|
until no more candidates can be removed or a list is empty (failure). */
|
|
|
|
static int reduceMutual(List<List<BitSet>> cols, List<List<BitSet>> rows) {
|
|
int countRemoved1 = reduce(cols, rows);
|
|
if (countRemoved1 == -1)
|
|
return -1;
|
|
|
|
int countRemoved2 = reduce(rows, cols);
|
|
if (countRemoved2 == -1)
|
|
return -1;
|
|
|
|
return countRemoved1 + countRemoved2;
|
|
}
|
|
|
|
static int reduce(List<List<BitSet>> a, List<List<BitSet>> b) {
|
|
int countRemoved = 0;
|
|
|
|
for (int i = 0; i < a.size(); i++) {
|
|
|
|
BitSet commonOn = new BitSet();
|
|
commonOn.set(0, b.size());
|
|
BitSet commonOff = new BitSet();
|
|
|
|
// determine which values all candidates of ai have in common
|
|
for (BitSet candidate : a.get(i)) {
|
|
commonOn.and(candidate);
|
|
commonOff.or(candidate);
|
|
}
|
|
|
|
// remove from bj all candidates that don't share the forced values
|
|
for (int j = 0; j < b.size(); j++) {
|
|
final int fi = i, fj = j;
|
|
|
|
if (b.get(j).removeIf(cnd -> (commonOn.get(fj) && !cnd.get(fi))
|
|
|| (!commonOff.get(fj) && cnd.get(fi))))
|
|
countRemoved++;
|
|
|
|
if (b.get(j).isEmpty())
|
|
return -1;
|
|
}
|
|
}
|
|
return countRemoved;
|
|
}
|
|
}
|