Can you beat the British Intelligence? (Nonogram Solver)

20

2

It is time to embark on a perilous quest to defeat the British Intelligence. The aim of this challenge is to write the shortest code that will solve a Nonogram.

What is a Nonogram?

Nonogram Puzzle

The rules are simple. You have a grid of squares, which must be either filled in black or left blank. Beside each row of the grid are listed the lengths of the runs of black squares on that row. Above each column are listed the lengths of the runs of black squares in that column. Your aim is to find all black squares. In this puzzle type, the numbers are a form of discrete tomography that measures how many unbroken lines of filled-in squares there are in any given row or column. For example, a clue of "4 8 3" would mean there are sets of four, eight, and three filled squares, in that order, with at least one blank square between successive groups. [1][2]

So the solution to the above Nonogram would be:

Solved nonogram

Implementation Details

You can chose to represent the Nonogram however you would like and take it as an input in whatever way you deem fit for your language. Same goes for output. The aim of this challenge is to literally just get the job done; if you can solve the nonogram with whatever output your program gives, that is valid. One caveat is you can't use an online solver :)

This problem is very algorithmically challenging (np-complete) in that there is no completely efficient solution to it and as such, you won't be penalized for not being able to solve larger ones, although your answer will be heavily rewarded if it is able to handle big cases (see bonus). As a benchmark, my solution works for up to roughly 25x25 within 5-10 seconds. To allow for flexibility amongst different languages, solutions that take less than 5 mins for a 25x25 nonogram are good enough.

You may assume a puzzle in always a square NxN nonogram.

You can use this online nonogram puzzle maker to test your solutions.

Scoring

You are, of course, free to use any language you want and since this is code golf, the entries will be sorted in the order: accuracy -> length of code -> speed. However, don't be discouraged by code golfing languages, answers in all languages that show attempts at golfing in an interesting way will be upvoted!

Bonus

I actually learnt about Nonograms from a cryptographic Christmas card released by the British Intelligence here. The first part was basically a massive 25x25 Nonogram. If your solution is able to solve this, you will get kudos :)

To make your life easier in terms of data entry, I have provided how I represented the data for this specific puzzle for your free use. The first 25 lines are the row clues, followed by a '-' separator line, followed by 25 lines of the col clues, followed by a '#' separator line, and then a representation of the grid with the square clues filled in.

7 3 1 1 7
1 1 2 2 1 1
1 3 1 3 1 1 3 1
1 3 1 1 6 1 3 1
1 3 1 5 2 1 3 1
1 1 2 1 1
7 1 1 1 1 1 7
3 3
1 2 3 1 1 3 1 1 2
1 1 3 2 1 1
4 1 4 2 1 2
1 1 1 1 1 4 1 3
2 1 1 1 2 5
3 2 2 6 3 1
1 9 1 1 2 1
2 1 2 2 3 1
3 1 1 1 1 5 1
1 2 2 5
7 1 2 1 1 1 3
1 1 2 1 2 2 1
1 3 1 4 5 1
1 3 1 3 10 2
1 3 1 1 6 6
1 1 2 1 1 2
7 2 1 2 5
-
7 2 1 1 7
1 1 2 2 1 1
1 3 1 3 1 3 1 3 1
1 3 1 1 5 1 3 1
1 3 1 1 4 1 3 1
1 1 1 2 1 1
7 1 1 1 1 1 7
1 1 3
2 1 2 1 8 2 1
2 2 1 2 1 1 1 2
1 7 3 2 1
1 2 3 1 1 1 1 1
4 1 1 2 6
3 3 1 1 1 3 1
1 2 5 2 2
2 2 1 1 1 1 1 2 1
1 3 3 2 1 8 1
6 2 1
7 1 4 1 1 3
1 1 1 1 4
1 3 1 3 7 1
1 3 1 1 1 2 1 1 4
1 3 1 4 3 3
1 1 2 2 2 6 1
7 1 3 2 1 1
#
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 1 0 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

And here is a slightly different version for your convenience; a comma separated tuple (row, col) where each element is list of lists.

([[7, 3, 1, 1, 7],
  [1, 1, 2, 2, 1, 1],
  [1, 3, 1, 3, 1, 1, 3, 1],
  [1, 3, 1, 1, 6, 1, 3, 1],
  [1, 3, 1, 5, 2, 1, 3, 1],
  [1, 1, 2, 1, 1],
  [7, 1, 1, 1, 1, 1, 7],
  [3, 3],
  [1, 2, 3, 1, 1, 3, 1, 1, 2],
  [1, 1, 3, 2, 1, 1],
  [4, 1, 4, 2, 1, 2],
  [1, 1, 1, 1, 1, 4, 1, 3],
  [2, 1, 1, 1, 2, 5],
  [3, 2, 2, 6, 3, 1],
  [1, 9, 1, 1, 2, 1],
  [2, 1, 2, 2, 3, 1],
  [3, 1, 1, 1, 1, 5, 1],
  [1, 2, 2, 5],
  [7, 1, 2, 1, 1, 1, 3],
  [1, 1, 2, 1, 2, 2, 1],
  [1, 3, 1, 4, 5, 1],
  [1, 3, 1, 3, 10, 2],
  [1, 3, 1, 1, 6, 6],
  [1, 1, 2, 1, 1, 2],
  [7, 2, 1, 2, 5]],
 [[7, 2, 1, 1, 7],
  [1, 1, 2, 2, 1, 1],
  [1, 3, 1, 3, 1, 3, 1, 3, 1],
  [1, 3, 1, 1, 5, 1, 3, 1],
  [1, 3, 1, 1, 4, 1, 3, 1],
  [1, 1, 1, 2, 1, 1],
  [7, 1, 1, 1, 1, 1, 7],
  [1, 1, 3],
  [2, 1, 2, 1, 8, 2, 1],
  [2, 2, 1, 2, 1, 1, 1, 2],
  [1, 7, 3, 2, 1],
  [1, 2, 3, 1, 1, 1, 1, 1],
  [4, 1, 1, 2, 6],
  [3, 3, 1, 1, 1, 3, 1],
  [1, 2, 5, 2, 2],
  [2, 2, 1, 1, 1, 1, 1, 2, 1],
  [1, 3, 3, 2, 1, 8, 1],
  [6, 2, 1],
  [7, 1, 4, 1, 1, 3],
  [1, 1, 1, 1, 4],
  [1, 3, 1, 3, 7, 1],
  [1, 3, 1, 1, 1, 2, 1, 1, 4],
  [1, 3, 1, 4, 3, 3],
  [1, 1, 2, 2, 2, 6, 1],
  [7, 1, 3, 2, 1, 1]])

gowrath

Posted 2016-08-30T15:26:55.477

Reputation: 885

Sadly my website is down but it used to have a reasonably fast Nonogram solver; 5-10 minutes sounds excessive. – Neil – 2016-08-30T15:33:20.310

Related – AdmBorkBork – 2016-08-30T15:34:16.313

@Neil Grand! What language did you write it in? I was generous with the time because I didn't want to restrict users of certain slower languages. My solver works in under 10 seconds last I checked. – gowrath – 2016-08-30T15:35:16.717

The way you worded it confused me into thinking your solver takes 10 minutes. Anyway, my solver was written in Java, but it probably wasn't as fast as 10 seconds (then again it would update the screen to show its thinking, which probably slowed it down). – Neil – 2016-08-30T15:45:52.603

Can we safely assume that the grid is always a NxN square? And BTW, thanks for putting the right name on a puzzle I've seen my wife playing with some times ago. :-) (Color Cross on Facebook) – Arnauld – 2016-08-30T15:57:02.877

@Arnauld Yes you can assume it is an NxN square :) – gowrath – 2016-08-30T15:58:19.977

Can we assume that there is only one solution? – Damien – 2016-08-30T16:55:13.670

@Damien If there is more than one solution, your code should find any one of them. Is there a specific advantage to being able to assume there is only one solution? – gowrath – 2016-08-30T16:56:49.813

I'm not sure yet. But I find it easier when the solution is unique – Damien – 2016-08-30T17:13:23.597

@Damien Go for it then :) – gowrath – 2016-08-30T17:23:19.283

Is it a "nonogram" or "monogram?" You use monogram twice in the impl details section... – Socratic Phoenix – 2016-08-30T18:38:11.680

@SocraticPhoenix It's nonogram; damned autocorrect. Will fix it now. – gowrath – 2016-08-30T18:47:57.460

1http://codegolf.stackexchange.com/q/30081/21348 Related – edc65 – 2016-08-30T20:59:28.510

Are there going to be empty rows? – ThreeFx – 2016-08-31T00:07:14.447

@mbomb007 Read the bonus section. The British intelligence released a Nonogram as part 1 of a series of cryptographic challenges last christmas (possibly for employment purposes). As far as I know, no one actually managed to finish all of the challenges. – gowrath – 2016-09-02T14:06:21.410

2 question: do we need to be able to handle unsolvable onces? /// Can I create a random answer, check if it work, if not repeat? (eg bogsort?) ^^ – dwana – 2016-09-02T14:09:24.873

@dwana Your solution probably has to be deterministic. – mbomb007 – 2016-09-02T14:12:49.863

1@dwana You don't need to worry about unsolvable cases. As for the random answer, on a 25x25 nonogram, you have 2^625 possible configurations. In context, that's more than twice the number of atoms in the known universe (i.e. if you used each atom in the universe as a bit, you still wouldn't have enough space to store the possibilities). In terms of time, if it took you a nano second (generous) to check the validity of each configuration, it would take 7 lifetimes of the universe for the code to finish running :) – gowrath – 2016-09-02T14:13:10.263

1Ty for clarifying unsolvables cases. (+ i have a magical PC the validates an answer in ~ 2.1546362E-186 seconds) – dwana – 2016-09-02T14:30:30.087

@dwana Unfortunately for you, it will still take ~4.5E124 years to complete. – mbomb007 – 2016-09-02T15:30:20.433

Is there a bug in my code or does the Christmas Puzzle have no solution? My backtrace cannot even solve the first 8 rows (returns "no solution" after about half a million iterations). – Titus – 2016-09-02T17:48:59.483

@Titus It definitely has a solution, although my data entry may have been shoddy. You can check against the original link in the bonus section of the question to validate the data. – gowrath – 2016-09-02T17:52:13.883

No, the port is correct. Must be a bug somewhere. Need a smaller puzzle to test; the tiny one is always correct. – Titus – 2016-09-02T18:01:23.667

@Titus Try the 10x10s generated by the website linked in the text – gowrath – 2016-09-02T18:03:07.203

I´m done. Can I get a /10 bonus for not using a golfing language? ;-) – Titus – 2016-09-03T19:10:03.230

1Your CSV has no square hints. Here´s some JS to generate them: s=[].fill([].fill(0,0,25),0,25);s[3][3]=s[3][4]=s3[3][12]=s3[3][13]=s3[3][21]=s[8][6]=s[8][7]=s[8][10]=s[8][14]=s[8][15]=s[8][18]=s[16][6]=s[16][11]=s[16][16]=s[16][20]=s[21][3]=s[21][4]=s[21][9]=s[21][10]=s[21][15]=s[21][20]=s[21][21]=1; – Titus – 2016-09-03T19:10:12.800

@gowrath Your tuple of lists which you say is (row, col) is actually (col, row). You should swap it around if you want it to match your other data format. – mbomb007 – 2016-09-08T14:32:05.450

Additional Potential Point of Interest: The smallest unsolvable case is [1,1],[1,1] – HyperNeutrino – 2017-03-28T13:39:49.750

Answers

5

Brachylog, 70 69 bytes

[R:C]hlL~l:L:1f=.:3aR,.z:3aC,
tL,?he##ElL,E:2a
.<2,_1<
@b:4f:la
e.h1,

This takes a list of two lists (first the rows indicators, then the column ones). Each indicator is itself a list (for situtions like [3,1] on one row).

This version takes about 3 minutes to solve the 5 by 5 example of the challenge.

Way more efficient version, 91 bytes

[R:C]hlL~l:L:1f:Cz:3az:Rz:3a=.:4aR,.z:4aC,
tL,?he##ElL,E:2a
.<2,_1<
:+a#=,?h
@b:5f:la
e.h1,

Try it online!

This one is not complete brute force: the only difference is that this one imposes constraints on the values of cells such that the number of 1s in each row and column matches the numbers given as indicators in the input. The only brute force part is then in finding the one grid with those constraints for which the "blocks" of 1s match what is given as indication.

This one takes about 0.05 seconds on the 5 by 5 example of the challenge. This is still way too slow for the bonus case, as I have no idea how to express the blocks of ones separated by one or more zeroes in terms of constraints.

Explanation

I will explain below the 93 bytes version. The only difference between the two is the call to predicate 3 which doesn't exist in the 70 bytes version, and the numbering of the predicates (since there is one less).

  • Main predicate:

    [R:C]     Input = [R, C]
    hlL       The length of R is L
    ~l        Create a list of length L
    :L:1f     Each element of that list is a sublist of length L with cells 0 or 1 (Pred 1)
              %%% Part unique to the 93 bytes version
    :Cz       Zip the rows of the list of lists with C
    :3a       The sum of 1s in each row is equal to the sum of the indicators (Pred 3)
    z         Transpose
    :Rz       Zip the columns of the list of lists with R
    :3a       The sum of 1s in each column is equal to the sum of the indicators (Pred 3)
              %%%
    =.        Assign values to the cells of the list of lists which satisfy the constraints
    :4aR,     The blocks of 1s must match the indicators on rows
    .z        Transpose
    :4aC,     The blocks of 1s must match the indicators on columns
    
  • Predicate 1: Forces the rows to have a specific length, and that each cell is 0 or 1.

    tL,       L is the length given as second element of the input
    ?he       Take an element from the list
    ##ElL,    That element E is itself a list of length L
    E:2a      The elements of E are 0s and 1s (Pred 2)
    
  • Predicate 2: Constrain a variable to be either 0 or 1

    .<2,      Input = Output < 2
    _1<       Output > -1
    
  • Predicate 3: The sum of 1s in a list must be equal to the sum of indicators (e.g. if the indicator is [3:1] then the list must have sum 4)

    :+a       Sum the elements of the list and sum the indicator
    #=,       Both sums must be equal
    ?h        Output is the list
    
  • Predicate 4: Check that blocks of 1s match the indicator

    @b        Split the list in blocks of the same value
    :5f       Find all blocks of 1s (Pred 5)
    :la       The list of lengths of the blocks results in the indicator (given as output)
    
  • Predicate 5: True for blocks of 1s, false otherwise

    e.        Output is an element of the input
      h1,     Its first value is 1
    

Fatalize

Posted 2016-08-30T15:26:55.477

Reputation: 32 976

Feels like the perfect tool for the job. Looking forward to the explanation. – Emigna – 2016-09-02T09:04:06.660

@Fatalize This is fantastic, I was waiting for someone to use a prolog-esque language to do this. Have you tried it with the 25x25 case? I entered the data for you already

– gowrath – 2016-09-02T09:42:53.207

@gowrath I will run this on my computer this afternoon, we'll see what happens. – Fatalize – 2016-09-02T09:54:26.807

@Fatalize Seems to time out but I may be doing it wrong. I wouldn't completely rely on my data entry skills either :D – gowrath – 2016-09-02T09:56:15.553

@gowrath It times out on TIO, but I will run it on the offline interpreter directly on my computer. – Fatalize – 2016-09-02T09:59:05.850

@gowrath The second version still hasn't solved the big case after a few hours. The big problem is that I have no idea how to express the idea of blocks of 1 separated by at least one zero with constraints. – Fatalize – 2016-09-02T13:01:27.667

@Fatalize I could try and help out but your code is elvish to me :D. I'm a humble C/Python programmer. – gowrath – 2016-09-02T13:06:42.243

You can add efficiency by summing a row's clues and adding x-1 where x is the number of clues for that row. If that number equals the width of the board, then the solution of that row is known independently of the other rows/columns. Do the same for columns. See the 4th row from the bottom of the bonus challenge. – mbomb007 – 2016-09-02T14:18:58.237

To make this a lot more efficient, try the CLP(FD) constraint automaton/3. All it takes is to build a finite state machine that accepts the suitable number of consecutive ones and zeros between states. – mat – 2016-09-02T14:48:39.093

@mat Well, I can do my method on paper. :) – mbomb007 – 2016-09-02T14:49:37.877

@mat I will look into adding this to Brachylog, this seems like a useful constraint predicate to have. – Fatalize – 2016-09-02T14:50:38.270

9

Haskell, 242 230 201 199 177 163 160 149 131 bytes

import Data.Lists
m=map
a#b=[x|x<-m(chunk$length b).mapM id$[0,1]<$(a>>b),g x==a,g(transpose x)==b]
g=m$list[0]id.m sum.wordsBy(<1)

Finally under 200 bytes, credit to @Bergi. Huge thanks to @nimi for helping almost halving the size.

Wow. Almost at half size now, partly because of me but mainly because of @nimi.

The magic function is (#). It finds all solutions of a given nonogram.

This is able to solve all cases, but may be super slow, since it's complexity is about O(2^(len a * len b)). A quick benchmark revealed 86GB allocated for a 5x5 nonogram.

Fun fact: It works for all nonograms, not only square ones.


How it works:

  • a#b: Given lists of lists of integers which represent the number of squares, generate all grids (map(chunk$length b).mapM id$a>>b>>[[0,1]]) and filter the results to keep only the valid ones.
  • g: Given a potential nonogram it sums the runs of 1's horizontally.

ThreeFx

Posted 2016-08-30T15:26:55.477

Reputation: 1 435

You mean O(2^(len a * len b)), not O((len a * len b)^2). – Anders Kaseorg – 2016-08-30T18:23:10.820

@AndersKaseorg Right. Keep the million I accidentally implied there. :D – ThreeFx – 2016-08-30T18:24:31.160

1Another few bytes: m(chunk$l b) and replicate(l$a>>b) – Bergi – 2016-08-30T21:49:27.340

@ThreeFx 86GB :O... Btw could you briefly explain how to compile this? I only just started learning haskell and this is giving errors with ghc. Wanna test it out :) – gowrath – 2016-08-30T22:07:26.123

@gowrath What errors are you getting? You need to install the Haskell Platform which is the de facto standard distribution since it comes with many useful packages. – ThreeFx – 2016-08-30T22:15:40.313

@ThreeFx Nvm I figured it out (y). This is a fantastic solution! Can you make it feasibly work for the 25x25? – gowrath – 2016-08-30T22:42:57.597

@gowrath Not in that size, you'd need wayy more optimization. – ThreeFx – 2016-08-30T22:44:54.527

1import Data.Lists is enough, because it re-exports both Data.List and Data.List.Split. – nimi – 2016-08-30T22:46:46.737

@nimi Wow that is huge. Thanks! – ThreeFx – 2016-08-30T22:49:08.300

@nimi [0,1]<$a>>b doesn't work... – ThreeFx – 2016-08-30T23:16:52.440

Let us continue this discussion in chat.

– ThreeFx – 2016-08-30T23:18:30.223

@ThreeFx: Did you see my latest suggestions on chat?

– nimi – 2016-09-01T18:17:06.580

@nimi Yes I did but unfortunately didn't have time. I'll look at it this evening (EST). – ThreeFx – 2016-09-01T18:29:50.687

4

Pyth, 91 72 71 bytes

D:GHdRq@@QdG.nCf.)TrH8V^,01^hQ2=TcNhQ=Y1VhQ=*Y*:H@TH1:H@CTH2)IYjbmjkdTb

A program that takes input of a list of the form [size, [horizontal clues], [vertical clues]] where each clue is a list of integers (empty clues are the empty list, []), and prints every solution, newline separated, in the form of a binary grid where 1 is shaded and 0 is unshaded.

This is a brute force, so is roughly O(2^n^2). It starts taking a very long time for larger puzzles, but will solve any arbritrary size given sufficient time.

Try it online

How it works

The program generates every possible layout by taking the repeated Cartesian product of [0, 1] with a length equal to size^2. This is then split into chunks, giving a list for each horizontal line. Each line is run-length encoded, filtered by the presence of 1 and flattened, leaving the clue for that line. This is then checked against the input. The above process is repeated for the transpose of the chunks, checking the vertical lines. If there is a hit, each chunk is concatenated, and the concatenated chunks are joined on newlines and implicitly printed, with a trailing newline.

D:GHdRq@@QdG.nCf.)TrH8V^,01^hQ2=TcNhQ=Y1VhQ=*Y*:H@TH1:H@CTH2)IYjbmjkdTb  Program. Input: Q
                            hQ                                           Q[0], size
                           ^  2                                          Square
                        ,01                                              [0, 1]
                       ^                                                 Cartesian product
                      V                                     )            For N in the Cartesian product:
                                 cNhQ                                    Split N into Q[0] chunks
                               =T                                        Assign that to T
                                     =Y1                                 Y=1
                                        VhQ                              For H in range [0, Q[0]-1]:
D:GHd                                                                     def :(G, H, d)
                   rH8                                                     Run-length-encode(H)
               f.)T                                                        Filter by presence of 1 in character part
            .nC                                                            Transpose and flatten, giving the clue
       @@QdG                                                               Q[d][G], the relevant input clue
     Rq                                                                    Return clue==input clue
                                               :H@TH1                     :(H, T, 1)
                                                     :H@CTH2              :(H, transpose(T), 2)
                                           =*Y*                           Y=Y*product of above two
                                                             IY           If Y:
                                                                 mjkdT     Conacatenate each element of T
                                                               jb          Join on newlines
                                                                      b    Add a newline and implicitly print

Thanks to @Pietu1998 for some tips

TheBikingViking

Posted 2016-08-30T15:26:55.477

Reputation: 3 674

This may be the longest Pyth program I've ever seen – Business Cat – 2016-08-31T02:03:51.050

=ZhZ is equal to =hZ, and FN is equal to V. – PurkkaKoodari – 2016-08-31T05:02:40.667

@TheBikingViking What exactly do you mean given sufficient time? I'm fairly sure this wouldn't solve a 25x25 by now if you had started it from the conception of the universe. – gowrath – 2016-08-31T05:44:30.427

1@gowrath I'm also fairly sure of that! I am new to Pyth, and after the length of time this took me, I don't even want to consider trying to implement a better algorithm – TheBikingViking – 2016-08-31T20:55:24.610

2

Javascript (ES6), 401 386 333 bytes

This is an early attempt. It's not very efficient but I was curious to test a solution using regular expressions on the binary representation of the rows and columns.

For example, it will translate the clue [3,1] into the following regular expression:

/^0*1{3}0+1{1}0*$/

Right now, this version is not taking square clues into account. I will probably add this later.

Code

(c,r)=>{W=c.length;w=[];S=0;M=(n,p)=>eval(`/^0*${p.map(v=>`1{${v}}`).join`0+`}0*$/`).exec(n);R=(y,i=0)=>S||(w[y]=r[y][i],y+1<W?R(y+1):c.every((c,y)=>(n=0,w.map((_,x)=>n+=w[W-1-x][y]),M(n,c)))&&(S=w.join`
`),r[y][i+1]&&R(y,i+1));r=r.map(r=>[...Array(1<<W)].map((_,n)=>((1<<30)|n).toString(2).slice(-W)).filter(n=>M(n,r)));return R(0)}

Output

The solution is displayed in binary format. Such as:

00110
01110
11100
11101
00001

Test

This is a simple test on the example grid.

let f =
(c,r)=>{W=c.length;w=[];S=0;M=(n,p)=>eval(`/^0*${p.map(v=>`1{${v}}`).join`0+`}0*$/`).exec(n);R=(y,i=0)=>S||(w[y]=r[y][i],y+1<W?R(y+1):c.every((c,y)=>(n=0,w.map((_,x)=>n+=w[W-1-x][y]),M(n,c)))&&(S=w.join`
`),r[y][i+1]&&R(y,i+1));r=r.map(r=>[...Array(1<<W)].map((_,n)=>((1<<30)|n).toString(2).slice(-W)).filter(n=>M(n,r)));return R(0)}

console.log(f(
  [[2],[3],[4],[2],[2]],
  [[2],[3],[3],[3,1],[1]]
));

Arnauld

Posted 2016-08-30T15:26:55.477

Reputation: 111 334

nice idea. kills my browsers on the christmas puzzle, though. – Titus – 2016-09-03T19:16:26.967

2

Haskell, 109 bytes

Disclaimer: this is derived from @ThreeFx's answer. I helped him to golf down his answer but he seems to have lost interest to include my last substantial improvements, so I post them as new answer.

import Data.List
n=mapM id
a#b=[x|x<-n$(n$" #"<$a)<$b,g x==a,g(transpose x)==b]
g=map$max[0].map length.words

Usage example: [[2],[3],[3],[3,1],[1]] # [[2],[3],[4],[2],[2]] -> [[" ## "," ### ","### ","### #"," #"]].

Brute force. Try all combinations of and #, split int chunks of #, count the lengths and compare to the input.

nimi

Posted 2016-08-30T15:26:55.477

Reputation: 34 639

1

PHP, 751 833 (720) 753 724 726 710 691 680 682 bytes

I was eager to build a specialised sequence increment and try my cartesian generator once more;
but dropped the cartesian in favour of backtracking to solve the large puzzle faster.

$p=[];foreach($r as$y=>$h){for($d=[2-($n=count($h)+1)+$u=-array_sum($h)+$w=count($r)]+array_fill($i=0,$n,1),$d[$n-1]=0;$i<1;$d[0]+=$u-array_sum($d)){$o=$x=0;foreach($d as$i=>$v)for($x+=$v,$k=$h[$i];$k--;)$o+=1<<$x++;if(($s[$y]|$o)==$o){$p[$y][]=$o;$q[$y]++;}for($i=0;$i<$n-1&$d[$i]==($i?1:0);$i++);if(++$i<$n)for($d[$i]++;$i--;)$d[$i]=1;}}
function s($i,$m){global$c,$w,$p;for(;!$k&&$i[$m]--;$k=$k&$m<$w-1?s($i,$m+1):$k){for($k=1,$x=$w;$k&&$x--;){$h=$c[$x];for($v=$n=$z=$y=0;$k&&$y<=$m;$y++)$n=$n*($f=($p[$y][$i[$y]]>>$x&1)==$v)+$k=$f?:($v=!$v)||$n==$h[$z++];if($k&$v)$k=$n<=$h[$z];}}return$k?is_array($k)?$k:$i:0;}
foreach(s($q,0)as$y=>$o)echo strrev(sprintf("\n%0{$w}b",$p[$y][$o]));
  • expects hints in arrays $r for row hints, $c for column hints and $s for square hints.
  • throws invalid argument supplied for foreach if it finds no solution.
  • to get the correct byte count, use a physical \n and remove the other two line breaks.

description

1) from row hints
generate possible rows that satisfy the square hints
and remember their counts for each row index.

2) backtrack over the row combinations:
If the combination satisfies the column hints, seek deeper or return successful combination,
else try next possibility for this row

3) print solution


The last golfing had severe impact on the performance;
but I removed profiling assignments for the final benchmarks.

Replace $n=$n*($f=($p[$y][$i[$y]]>>$x&1)==$v)+$k=$f?:($v=!$v)||$n==$h[$z++];
with if(($p[$y][$i[$y]]>>$x&1)-$v){$k=($v=!$v)||$n==$h[$z++];$n=1;}else$n++;
to undo the last golfing step.

examples

For the small example (17 to 21 around 12 8 7 6.7 5.3 ms), use

$r=[[2],[3],[3],[3,1],[1]];$c=[[2],[3],[4],[2],[2]];$s=[0,0,0,0,0];

for the christmas puzzle:

  • killed my small home server with the old solution
  • killed the browser with test outputs
  • now solved in 50 37.8 45.5 around 36 seconds

put data from question to a file christmas.nonogram and use this code to import:

$t=r;foreach(file('christmas.nonogram')as$h)if('-'==$h=trim($h))$t=c;elseif('#'==$h){$t=s;$f=count($h).b;}else
{$v=explode(' ',$h);if(s==$t)for($h=$v,$v=0,$b=1;count($h);$b*=2)$v+=$b*array_shift($h);${$t}[]=$v;}

breakdown

$p=[];  // must init $p to array or `$p[$y][]=$o;` will fail
foreach($r as$y=>$h)
{
    // walk $d through all combinations of $n=`hint count+1` numbers that sum up to $u=`width-hint sum`
    // (possible `0` hints for $h) - first and last number can be 0, all others are >0
    for(
        $d=[2-
            ($n=count($h)+1)+               // count(0 hint)=count(1 hint)+1
            $u=-array_sum($h)+$w=count($r)  // sum(0 hint) = width-sum(1 hint)
        ]                           // index 0 to max value $u-$n+2
        +array_fill($i=0,$n,1)      // other indexes to 1
        ,$d[$n-1]=0;                // last index to 0
                                    // --> first combination (little endian)
        $i<1;   // $i:0 before loop; -1 after increment; >=$n after the last combination
        $d[0]+=$u-array_sum($d) // (see below)
    )
    {
        // A: create row (binary value) from 1-hints $h and 0-hints $d
        $o=$x=0;
        foreach($d as$i=>$v)
            for($x+=$v,$k=$h[$i];$k--;)
                $o+=1<<$x++;
        // B: if $o satisfies the square hints
        if(($s[$y]|$o)==$o)
        {
            $p[$y][]=$o;    // add to possible combinations
            $q[$y]++;       // increase possibility counter
        }
        // C: increase $d
            // find lowest index with a value>min
                // this loop doesn´t need to go to the last index:
                // if all previous values are min, there is nothing left to increase
        for($i=0;$i<$n-1&$d[$i]==($i?1:0);$i++);
        if(++$i<$n)             // index one up; increase $d if possible
            for($d[$i]++        // increase this value
            ;$i--;)$d[$i]=1;    // reset everything below to 1
            // adjust $d[0] to have the correct sum (loop post condition)
    }
}

// search solution: with backtracking on the row combinations ...
function s($i,$m)
{
    global $c,$w,$p;
    for(;
        !$k // solution not yet found
        &&$i[$m]    // if $i[$m]==0, the previous iteration was the last one on this row: no solution
            --;     // decrease possibility index for row $m
        $k=$k&$m<$w-1? s($i,$m+1) : $k      // if ok, seek deeper while last row not reached ($m<$w-1)
    )
    {
        // test if the field so far satisfies the column hints: loop $x through columns
        for($k=1,$x=$w;$k&&$x--;)   // ok while $k is true
        {
            $h=$c[$x];
            // test column hints on the current combination: loop $y through rows up to $m
            for($v=$n=$z=   // $v=temporary value, $n=temporary hint, $z=hint index
                $y=0;$k&&$y<=$m;$y++)
                // if value has not changed, increase $n. if not, reset $n to 1
                // (or 0 for $k=false; in that case $n is irrelevant)
                $n=$n*  
                    // $f=false (int 0) when value has changed, true (1) if not
                    ($f=($p[$y][$i[$y]]>>$x&1)==$v)
                    +$k=$f?:    // ok if value has NOT changed, else
                        ($v=!$v)        // invert value. ok if value was 0
                        || $n==$h[$z    // value was 1: ok if temp hint equals current sub-hint
                        ++]             // next sub-hint
                ;
            // if there is a possibly incomplete hint ($v==1)
            // the incomplete hint ($n) must be <= the next sub-hint ($c[x][$z])
            // if $n was <$h[$z] in the last row, the previous column hints would not have matched
            if($k&$v)$k=$n<=$h[$z];
        }
        // ok: seek deeper (loop post condition)
        // not ok: try next possibility (loop pre condition)
    }
    return$k?is_array($k)?$k:$i:0;  // return solution if solved, 0 if not
}

// print solution
foreach(s($q,0)as$y=>$o)echo strrev(sprintf("\n%0{$w}b",$p[$y][$o]));

Titus

Posted 2016-08-30T15:26:55.477

Reputation: 13 814

1The large example kills my small home server (500 - Internal Server Error). The combinations are ready after 15 secons, but the cartesian product has 1.823E+61 members. (The 7th and 22nd row only have one solution btw.) Algorithm must be improved. – Titus – 2016-08-31T14:36:06.173

I think this could be sped up if you used recursive backtracking. Nevertheless, great work! – gowrath – 2016-08-31T21:05:22.177

@gowrath: backtracking gives a bit and even saves bytes ... integer with bit arithmetics give about 50% speed but increases the size (have to find out yet how much it exactly costs) ... I´m still on it. – Titus – 2016-09-02T17:44:40.520

@gowrath: I chased my bug down; it was in the increment (where else?): $d has to be in correct order for foreach – Titus – 2016-09-03T11:51:10.717