it\'s Friday afternoon, let\'s have a fun puzzle/algorithm problem to solve.
One of my favorite Nintendo DS games is Picross DS. The game is quite simple, it involves so
Rather than trying to place the "first" row, it will cut down on your search substantially if you try to get information from the "most constrained" row or column, which may have several forced values. A quick indication is to add up all the values in a row/column and add #_of_values - 1, then look for the row/column with the biggest such value (or smallest difference between this value and the # of rows or columns, if the rows and columns differ). Thus if you have a 25x25 puzzle, and one of the rows is "5 1 1 6 1 1 3", that row has a value of 24, which means it is very constrained - the relative position of all but one of the blank squares in that row is known, and the last blank square can be in any of 8 different relative positions. So for each group of filled squares, there are only two possibilities: extra blank square is to the left of this group, or extra blank square is to the right of this group. So, for example, five of the filled squares in that group of 6 are already known.
Once you have some forced values from one direction, that further constrains any of the groups from the other direction that intersect with the known information. So the best approach is likely to be working back and forth between columns and rows as more information becomes known, which is pretty much the way you need to solve it by hand.
Yes, the problem is NP-complete, but that only means that you are (pretty much) stuck with exponentially growing run-times as the size of the puzzle grows. But in real life puzzle sizes don't grow. Hardly anyone bothers to build puzzles that are bigger than 100x100 and the vast majority are smaller than 50x50. Building a solver that will solve 95% of the puzzles published in books and magazines in a fraction of second is actually not particularly challenging. A fairly straight-forward depth-first search system will do it.
What's less obvious is that there is a small fraction of puzzles that are quite nasty and will cause run-times to explode for most simple solvers. Most of these are badly designed puzzles that are also difficult or impossible for humans to solve, but some are particularly clever puzzles that experienced human solvers can readily solve, using deeper insights than most AI programs can manage.
I've written a solver of my own that will solve most puzzles very quickly, and I've done a survey of many other solvers with benchmark results comparing their performance. I also have a discussion of an interesting class of hard puzzles (the Domino puzzles) that demonstrates how some puzzles that are not hard for a clever human prove very hard for most programs. Links to my solver and the Domino Puzzle will be found in the survey.
I think there is still a lot of room for improvement and would encourage people with fresh ideas to take a shot at it. But it's worth noting that the obvious things have been done and there isn't much use in doing them again.
Some months ago I wrote a solver for nonograms on C++. It just look for all permissible positions for shaded and blank cells. And on every solution step it looks if every position is ok. So here is the result for Chad Birch's nonogram with timing: http://i.stack.imgur.com/aW95s.png.
And I tried my solver for Mikko Rantanen's nongram too: http://i.stack.imgur.com/o1J6I.png.
Here is what I found:
All NP Complete problems have the same feel. It's always easy to solve the next 80% of remaining cases. For example, nanograms decompose into a single lines. One can write a routine, solve_one_line(old_state, line_definition, new_state)
to update what is known about a single line, then keep iterating over rows and columns. Then it fails on a few cases, so you write something to solve 80% of those cases. Then you add random numbers and solve every case you have ever found, but it is possible to construct an optimally bad one.
Other games following this pattern are MineSweeper and Soduku.
Thinking in Parallel is hard. For example, you might figure out that the solve_one_line
routine above will not affect another row if running on a row or another column if running on a column.
Now you get:
all_until_completion(rows):
solve_one_line(...)
all_until_completion(cols):
solve_one_line(...)
This gets you up to running 20 cores (on a 20x20) without data locking or anything. Then you think about running on a graphics card with each cell a processor. Then you notice how much time has passed.
One time I felt old was looking at modern computer science textbook where O(n) notation was replaced with O(n, p) notation because no one would care to evaluate an algorithm for a single processor. The 8 queens solution is a great, fast recursive algorithm with fast-fail, efficient memory use, and only runs on one processor.
Problems are good excuses to play. Grinding out more of the same gets boring. Look through a list of technologies with which you might want more experience: behavior-driven testing; dependency injection; pure functional programming; neural nets; genetic algorithms; fast, sloppy and out of control; GPGPU; OCR; example-driven learning; crowdsourcing; etc. Pick one and try to use it somehow to solve the problem. Sometimes the goal is not to solve the problem but to wander through unknown territory.
Contribute something.* This can be a simple as a write-up. Better might be a repository of hundreds of nanograms so others can play. [Let me know if a repository exists, otherwise I will make one]. Start contributing as soon as you have something you find kind of neat. Heed the Klingon words: Perhaps today is a good day to die; I say we ship it.
So write bizarre, parallel solutions to NP problems and share them. And have a great day!
This seems like a pretty obvious case for depth first search with backtracking, as with the "n queens" problem. The sort of cute part is that as well as placing rows/columns, you can shift the blocks.
Okay, here's an outline.
Start with an empty board, place the first row
Now, with that board, place a second row, check it against the column constraints. If it passes, recursively try the next row against that state; if it doesn't pass, then try the next possible placement of that row.
If at any time you run out of possible placements of a row that satisfy the constraints, then the puzzle has no solution. Otherwise, when you run out of rowns, you've solved the problem.
It's convenient that these rows can be treated as binary numbers, so there is a natural ordering to the possibilities.
I don't have enough time right now to flesh out a solution, but this is how I would tackle it.
"function1" determine the possible combinations for a row or column given the constraints of the numbers on the top or side, and the squares that are already filled out and those that have been emptied.
"function2" take the output from function1 and logically and all the results together - the spots with ones could be filled in.
"function3" take the output from function1 and logically or all the results together - the spots with zeros could be emptied.
keep applying function2 and function3 to all the rows and collumns until no more boxes are emptied or filled in. If the puzzle is solved then, you are done.
If the puzzle is not solved, then take a row or column that has the fewest possibilities and apply the first possibility. Then apply function2 and function3 to the new board. If it leads to a contradiction (0 possibilities for a row or column) then un-apply the possibility and try a different one. Keep recursing like this until a solution is found.