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> 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 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> getCandidates(String[] data, int len) { List> result = new ArrayList<>(); for (String s : data) { List lst = new LinkedList<>(); int sumChars = s.chars().map(c -> c - 'A' + 1).sum(); List 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 genSequence(List ones, int numZeros) { if (ones.isEmpty()) return asList(repeat(numZeros, "0")); List result = new ArrayList<>(); for (int x = 1; x < numZeros - ones.size() + 2; x++) { List 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> cols, List> 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> a, List> 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; } }