151 lines
5.3 KiB
Julia
151 lines
5.3 KiB
Julia
using Base.Iterators
|
|
|
|
struct NonogramPuzzle
|
|
nrows::Int
|
|
ncols::Int
|
|
xhints::Vector{Vector{Int}}
|
|
yhints::Vector{Vector{Int}}
|
|
solutions:: Vector{Any}
|
|
NonogramPuzzle(xh, yh) = new(length(xh), length(yh), xh, yh, Vector{NTuple{4,Array{Int64,1}}}())
|
|
end
|
|
|
|
ycols2xrows(ycols) = [[ycols[i][j] for i in eachindex(ycols)] for j in eachindex(ycols[1])]
|
|
|
|
function hintsfromcol(rowvec, col, nrows)
|
|
hints = Vector{Int}()
|
|
hintrun = 0
|
|
for row in rowvec
|
|
if row[col] != 0
|
|
hintrun += 1
|
|
if col == nrows
|
|
push!(hints, hintrun)
|
|
end
|
|
elseif hintrun > 0
|
|
push!(hints, hintrun)
|
|
hintrun = 0
|
|
end
|
|
end
|
|
hints
|
|
end
|
|
|
|
function nonoblocks(hints, len)
|
|
minsized(arr) = vcat(map(x -> vcat(fill(1, x), [0]), arr)...)[1:end-1]
|
|
minlen(arr) = sum(arr) + length(arr) - 1
|
|
if isempty(hints)
|
|
return fill(0, len)
|
|
elseif minlen(hints) == len
|
|
return minsized(hints)
|
|
end
|
|
possibilities = Vector{Vector{Int}}()
|
|
allbuthead = hints[2:end]
|
|
for leftspace in 0:(len - minlen(hints))
|
|
header = vcat(fill(0, leftspace), fill(1, hints[1]), [0])
|
|
rightspace = len - length(header)
|
|
if isempty(allbuthead)
|
|
push!(possibilities, rightspace <= 0 ? header[1:len] : vcat(header, fill(0, rightspace)))
|
|
elseif minlen(allbuthead) == rightspace
|
|
push!(possibilities, vcat(header, minsized(allbuthead)))
|
|
else
|
|
foreach(x -> push!(possibilities, vcat(header, x)), nonoblocks(allbuthead, rightspace))
|
|
end
|
|
end
|
|
possibilities
|
|
end
|
|
|
|
function exclude!(xchoices, ychoices)
|
|
andvec(a) = findall(x -> x == 1, foldl((x, y) -> [x[i] & y[i] for i in 1:length(x)], a))
|
|
orvec(a) = findall(x -> x == 0, foldl((x, y) -> [x[i] | y[i] for i in 1:length(x)], a))
|
|
filterbyval!(arr, val, pos) = if !isempty(arr) filter!(x -> x[pos] == val, arr); end
|
|
ensurevecvec(arr::Vector{Vector{Int}}) = arr
|
|
ensurevecvec(arr::Vector{Int}) = [arr]
|
|
function excl!(choices, otherchoices)
|
|
for i in 1:length(choices)
|
|
if length(choices[i]) > 0
|
|
all1 = andvec(choices[i])
|
|
all0 = orvec(choices[i])
|
|
foreach(n -> filterbyval!(otherchoices[n], 1, i), all1)
|
|
foreach(n -> filterbyval!(otherchoices[n], 0, i), all0)
|
|
end
|
|
end
|
|
end
|
|
xclude!(x, y) = (excl!(x, y); x = map(ensurevecvec, x); y = map(ensurevecvec, y); (x, y))
|
|
xlen, ylen = sum(map(length, xchoices)), sum(map(length, ychoices))
|
|
while true
|
|
ychoices, xchoices = xclude!(ychoices, xchoices)
|
|
if any(isempty, xchoices)
|
|
return
|
|
end
|
|
xchoices, ychoices = xclude!(xchoices, ychoices)
|
|
if any(isempty, ychoices)
|
|
return
|
|
end
|
|
newxlen, newylen = sum(map(length, xchoices)), sum(map(length, ychoices))
|
|
if newxlen == xlen && newylen == ylen
|
|
return
|
|
end
|
|
xlen, ylen = newxlen, newylen
|
|
end
|
|
end
|
|
|
|
function trygrids(nonogram)
|
|
xchoices = [nonoblocks(nonogram.xhints[i], nonogram.ncols) for i in 1:nonogram.nrows]
|
|
ychoices = [nonoblocks(nonogram.yhints[i], nonogram.nrows) for i in 1:nonogram.ncols]
|
|
exclude!(xchoices, ychoices)
|
|
if all(x -> length(x) == 1, xchoices)
|
|
println("Unique solution.")
|
|
push!(nonogram.solutions, [x[1] for x in xchoices])
|
|
elseif all(x -> length(x) == 1, ychoices)
|
|
println("Unique solution.")
|
|
ycols = [y[1] for y in ychoices]
|
|
push!(nonogram.solutions, ycols2xrows(ycols))
|
|
else
|
|
println("Brute force: $(prod(map(length, xchoices))) possibilities.")
|
|
for stack in product(xchoices...)
|
|
arr::Vector{Vector{Int}} = [i isa Vector ? i : [i] for i in stack]
|
|
if all(x -> length(x) == nonogram.ncols, arr) &&
|
|
all(y -> hintsfromcol(arr, y, nonogram.nrows) == nonogram.yhints[y], 1:nonogram.ncols)
|
|
push!(nonogram.solutions, arr)
|
|
end
|
|
end
|
|
nsoln = length(nonogram.solutions)
|
|
println(nsoln == 0 ? "No" : nsoln, " solutions.")
|
|
end
|
|
end
|
|
|
|
# The first puzzle below requires brute force, and the second has no solutions.
|
|
const testnonograms = """
|
|
B B A A
|
|
B B A A
|
|
|
|
B A A
|
|
A A A
|
|
|
|
C BA CB BB F AE F A B
|
|
AB CA AE GA E C D C
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
"""
|
|
|
|
function processtestpuzzles(txt)
|
|
solutiontxt(a) = (s = ""; for r in a for c in r; s *= (c == 0 ? "." : "#") end; s *= "\n" end; s)
|
|
txtline2ints(s) = [[UInt8(ch - 'A' + 1) for ch in r] for r in split(s, r"\s+")]
|
|
linepairs = uppercase.(string.(split(txt, "\n\n")))
|
|
pcount = 0
|
|
for xyhints in linepairs
|
|
xh, yh = map(x -> txtline2ints(strip(x)), split(xyhints, "\n"))
|
|
nonogram = NonogramPuzzle(xh, yh)
|
|
println("\nPuzzle $(pcount += 1):")
|
|
trygrids(nonogram)
|
|
foreach(x -> println(solutiontxt(x), "\n"), nonogram.solutions)
|
|
end
|
|
end
|
|
|
|
processtestpuzzles(testnonograms)
|