
Solving Sudoku with Prolog (2016) - hjek
https://www.metalevel.at/sudoku/
======
triska
Thank you very much for the publicity, I greatly appreciate your interest!

Prolog is extremely well-suited for solving combinatorial tasks like Sudoku
puzzles, and also for tough practical challenges such such as timetabling,
scheduling and allocation tasks on an industrial scale.

The key feature that makes Prolog so efficient and frequently used for such
tasks is _constraint propagation_ , provided via libraries or as a built-in
feature in many Prolog system. Fast and efficient constraint propagation is
often an important reason for buying a commercial Prolog system.

In this example, I am using CLP(FD/ℤ), constraint logic programming over
finite domains/integers, the amalgamation of constraint programming (CP) and
logic programming (LP), which blend especially seamlessly. I have uploaded a
short video that shows how it works for Sudoku puzzles:

[https://www.metalevel.at/prolog/videos/sudoku](https://www.metalevel.at/prolog/videos/sudoku)

If you are interested in constraint solving, a few related videos may also be
useful:

[https://www.metalevel.at/prolog/videos/n_queens](https://www.metalevel.at/prolog/videos/n_queens)

[https://www.metalevel.at/prolog/videos/knights_and_knaves](https://www.metalevel.at/prolog/videos/knights_and_knaves)

[https://www.metalevel.at/prolog/videos/map_colouring](https://www.metalevel.at/prolog/videos/map_colouring)

These videos are all work in progress, and they may be replaced by better
versions at any time. However, the links above will always point to the latest
version.

Enjoy!

~~~
hjek
I've truly enjoyed all the Prolog videos on your channel[0].

I had to program a timetabling system where each event had a specific set of
requirements, and your article and video about solving sudokus and the
n-queens helped me grasp the problem.

I have only been using Prolog for a month, but I could not go back to Lisp now
(at least not without MiniKanren). There's still plenty of stuff I don't
understand about Prolog, but it feels very expressive and powerful even with
just a superficial understanding of the language.

Thank you so much for your work! Your teaching materials are very accessible
for beginners. Hope you will one day make a video about DCGs (Definite Clause
Grammars). I wonder if reading Noam Chomsky will help my understanding of
them?

Since we are on Hacker News, I think it's interesting to look at PG's
experience with Prolog, which unfortunately hasn't been as rewarding as mine:

> _There are features, most notably Prolog-style pattern-matching, that seem
> to promise great savings in length, but turn out only to be useful for
> writing a few basic sequence operations like append, remove, and so on.
> Prolog is a great language for writing append; after that it 's all
> downhill._[1]

> _A way of expressing programs that was more abstract, but made your programs
> longer, would not be very enticing. (This is not just a hypothetical
> example. It happens in Prolog.)_ [2]

From watching your videos, it's easy to see how concise Prolog programs are
though. I find that my own Prolog programs also tend to be _extremely_ brief
(compared to Lisp or SQL).

[0]:
[https://invidio.us/channel/UCFFeNyzCEQDS4KCecugmotg](https://invidio.us/channel/UCFFeNyzCEQDS4KCecugmotg)

[1]:
[http://www.paulgraham.com/arcchallenge.html](http://www.paulgraham.com/arcchallenge.html)

[2]:
[http://www.paulgraham.com/arcll1.html](http://www.paulgraham.com/arcll1.html)

~~~
abecedarius
> more abstract, but made your programs longer

There are a few main reasons for Prolog code to come out longer than Lisp:

\- Prolog style is mostly first order -- there's not a direct equivalent of
lambda.

\- Mutation is also used less.

\- There's an equivalent of Lisp macros, but it's also less common.

\- You don't nest function calls, but pass common named arguments instead.

So I can see where he was coming from. On the other hand I'd guess he had his
Prolog experience before constraint logic programming was well supported, and
certainly before MiniKanren which gives back the conveniences of Lisp and
helps you take the pure-declarative subset further.

~~~
hjek
PG does indeed use a lot of mutation in his Arc code. "Karma" on `vote-for` on
HN is just incremented like this:

    
    
        (++ (karma i!by) (case dir up 1 down -1))
    

At first this looks nice and simple, but when you have to write `unvote`,
you're in trouble (because not all votes increase karma, and you'll have to
figure out whether the particular vote being unvoted _did_ ).

I was reading through The Reasoned Schemer (which covers MiniKanren). They do
a lot of _unnesting_ when translating from functional to logical programming.
And yes, this does introduce an additional variable each time, making the
logical version slightly longer.

Apart from the `markdown` in the Hacker News code, there is an `unmarkdown`
function[0] (whose job is to _translate back_ from HTML to markdown!). In
Prolog you could have gotten away with one predicate for that. That's a 50%
saving just there. And you could perhaps use DCGs to cut it down further.

Not sure I about Prolog being mostly first order. Plenty of built-in
predicates that take predicate arguments, like `maplist`, `findall` and
`foldl` that seem to be as commonly used as in Lisp. You might be right about
lambdas missing though, so not sure you can emit a predicate (although you
could emit the _name_ of a predicate and call it). I'm too new to Prolog to
tell, really.

[0]:
[https://github.com/arclanguage/anarki/blob/master/lib/markdo...](https://github.com/arclanguage/anarki/blob/master/lib/markdown.arc#L120)

~~~
abecedarius
Re: lambda expressions, it turns out there was a proposal to add them. Maybe
it or something like it is in common use by now; I wouldn't know.
[http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-
Hi...](http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord.html)

(It's true that you can write higher-order programs without them. The style
was often clumsier and used less, at least as I saw it back when I read much
Prolog.)

Re: unnesting, I've toyed with the idea of a slightly fancier Prolog syntax
that lets you write nested calls which get desugared to the use of an extra
parameter. Of course they'd have to look distinct from functors as data.

------
OJFord
I love the simplicity or prolog; I often think it/datalog would be great as a
query language for applications.

e.g. instead of Jira having its JQL or whatever, it would just have a prelude,
then you'd write things like:

    
    
        my_team_working_on_it(ticket) :-
            assigned(ticket, user),
            on_team(user, team),
            on_team(current_user, team).
    

Or to search for tickets assigned to you (like defining a board filter):

    
    
        assigned(_, current_user)

~~~
elamje
Check out datomic database

~~~
OJFord
Thought it rang a bell from a databases lecture - seems it 'extends' datalog:

[https://docs.datomic.com/cloud/whatis/data-
model.html#datalo...](https://docs.datomic.com/cloud/whatis/data-
model.html#datalog)

Thanks for reminding me, must find an excuse to play with it!

------
ggggtez
Prolog is good for discrete search like this, mostly because it's just hiding
the underlying search algorithm that other languages would be to be explicit
about (but as a result, other languages can search more efficiently).

~~~
segmondy
All computer science problems can be reduced to sorting & searching. ;-D

~~~
randomsearch
Is this really true? I was wondering about complexity analysis, for example.

~~~
ggggtez
No it's not true. I think if you called multiplication a sorting algorithm it
would be really dishonest.

------
segmondy
Solving Sudoku smartly with Prolog.

The nice thing with Prolog is that if you don't understand how to program but
you understand the rules/logic of the problem. In this case you understand the
rules of Sudoku, you can define the board and the rules and Prolog will brute
force it for you. Harder to do in other languages.

------
maebert
I can’t help but to feel delightfully nostalgic whenever I read about Prolog,
thanks for writing this up. If you’re interested, I’ve created a set of Prolog
exercises about 10 years ago when I was teaching this in college, including
one for “advanced Sudoku”:

[https://github.com/maebert/prolog_puzzles/blob/master/readme...](https://github.com/maebert/prolog_puzzles/blob/master/readme.md#sudoku)

------
alexbanks
This was a final project in a course I took in college surveying programming
languages. I thought Prolog was insanely interesting. Through most of the
project I thought it was crazy, but when I finished it made excellent sense.

Since then I've thought a lot about using Prolog for optimized job/task
scheduling, but I've never had an actual need to do it.

------
justkez
I did a module in my undergrad degree that used Prolog as the learning
tool/language. At the time I didn't _understand_ why Prolog was such a
different paradigm (declarative); all I remember is feeling a true sense of
joy writing prolog code and running it.

I've never found/made up a reason to try it out since then and no
language/system/paradigm has given me the same enjoyment since!

------
protomyth
I love the embedded PostScript in the source code. Its always fun to see how
other people write their code.

~~~
triska
Thank you for noting this!

PostScript is currently underappreciated, and I highly recommend learning it.
Among many other uses, I like to use PostScript as a portable animation
language to visualize search processes like in this case.

Also, programming PostScript is great fun and in a sense quite timeless: In
all likelihood, these definitions will work without change also in the distant
future, because PostScript is so widely spread and frequently used.

Newer graphics languages and concepts also take many ideas from PostScript,
but PostScript is in my experience still the most fun and flexible of them, at
least for a very large set of important use cases.

------
pezo1919
The only thing prevents me using prolog for proper tasks is not getting its
IO: reading/writing files: csv, json.

Any cool (video) tuts on it? I searched a lot but did not find anything
"easy".

~~~
hjek
Say you have a file, `foo.json`, with the content:

    
    
       {"foo":"bar"} 
    

Run `swipl` and first load the JSON library:

    
    
        ?- [library(http/json)].
        true.
    

Then open the file `foo.json` (in read mode) as Stream which JSON read
translates to Term:

    
    
        ?- open('foo.json', read, Stream), json_read(Stream, Term).
        Stream = <stream>(0x557f0cbfb1c0),
        Term = json([foo=bar]).
    

(Both instances of `Stream` are the same because of unification.)

Say `foo.csv` containts:

    
    
        foo,bar
    

Then go:

    
    
        ?- [library(csv)].
        true.
        
        ?- csv_read_file("foo.csv",Rows).
        Rows = [row(foo, bar)].
    

Triska's videos are quite theoretical. Anne Ogborn has a lot of good real
world programming tutorials[0]. Can highly recommend the web app tutorial[1].

I don't know any good videos that cover this. I'm happy to help with what I
can though, and I've also found the ##Prolog IRC on Freenode helpful.

[0]:
[http://www.pathwayslms.com/swipltuts/](http://www.pathwayslms.com/swipltuts/)

[1]:
[http://www.pathwayslms.com/swipltuts/html/index.html](http://www.pathwayslms.com/swipltuts/html/index.html)

------
bluetwo
Curious about 2 things:

1) Can it solve especially hard Sudoku puzzles? (ie, expert level where humans
often get stuck)

2) Is there a resource somewhere that lists the types of Prolog problems that
it excels at solving?

~~~
hjek
1) Yes, and (when there is only one unique solution) it can apparently be
found in Prolog _without doing an search at all_ by using constraint logic
programming. Check the video in the sudoku article.

2) I'm not the best at answering this as I'm quite new to Prolog, but I'll
try. I'd say a) anything you'd use a general purpose programming languge for,
b) anything you'd use SQL for, and c) anything that's too difficult to do in
either of those.

SWI Prolog[0] has a large standard library and feels very "batteries
included".

Perhaps what I wouldn't use it for is mobile apps..? (But then again, there's
Tau Prolog[1] which could probably work in a Cordova app).

[0]: [http://www.swi-prolog.org/](http://www.swi-prolog.org/)

[1]: [http://tau-prolog.org/](http://tau-prolog.org/)

~~~
taeric
I'm curious on your first point. There are puzzles that have multiple legal
plays but only a single solution. In those, guessing/searching is basically
required, right?

~~~
triska
A valid Sudoku puzzle is such that the given clues admit precisely one
solution.

Hence, if you somehow manage to take into account _all the initially available
information_ , then no search is required, because, in a sense, the given
hints already narrow down the entire space of (ostensibly) possible
assignments to a single one.

There are constraint solvers that do this. For example, with the CLP(B)
version based on BDDs that I mentioned, the solver internally constructs a
Binary Decision Diagram (BDD) which is like a (ideally: more compact) truth
table of all assignments that satisfy the requirements.

Therefore, with the CLP(B) version I posted, _search is never necessary_ for
valid Sudoku puzzles, because they admit precisely one solution, and the
constraint solver deduces this unique solution by reasoning about the decision
diagram.

There's a severe drawback to this: The BDD may be exponential in the number of
variables. That's a problem, because there are dozens of variables in many
Sudoku puzzles. In practice, we are often lucky, and the worst-case space
requirements do not arise.

For comparison, the CLP(FD/ℤ) formulation is based on constraint propagation
that ensures a certain kind of consistency for all _individual_ constraints in
isolation, but not necessarily for the combination of all constraints, i.e.,
the problem as a whole.

In practice, at least for Sudoku puzzles, this also often leads to finding the
unique solution without any search. This may also be the case if only the
_minimum_ number of clues for a valid Sudoku puzzle (i.e., 17, per
[https://arxiv.org/abs/1201.0749](https://arxiv.org/abs/1201.0749)) is
specified, as the parent mentions. In general, search is still required. In
the concrete case of (standard) Sudoku, the required search, if there is any
at all, is typically very small, because the many constraints usually narrow
down the entire space considerably.

Reducing the amount of required search is the key attraction and goal of the
consistency techniques that are used by constraint systems. The available
systems differ in how much and how fast they are able to prune inconsistent
values, leading to notions such as global consistency, domain consistency,
bounds consistency etc. that give some indication of propagation strength.

~~~
taeric
I'll see if I can find my references again, but pretty sure this is not true.
You can have a specified puzzle that has a single solution but requires a
guess. I agree most are not this way, but they can be made.

~~~
triska
If you find such a puzzle, please let me know!

Assuming the CLP(B) solver is correctly implemented, the CLP(B) solution I
posted will give the unique solution without any search.

On the other hand, it is comparatively easy to find puzzles where the CLP(ℤ)
version will require search, even if only a small amount.

~~~
taeric
I'll have to dive on it more, but
[https://news.ycombinator.com/item?id=14246452](https://news.ycombinator.com/item?id=14246452)
is last time I remember discussing it. I thought the point was that sometimes
searches were needed. Did I misunderstand? Have things changed? (Neither would
surprise me.)

------
GavinMcG
Peter Norvig's Sudoku solver using constrain propagation is a nice
demonstration of the mini-Kanren approach.[0]

I spoke at RubyConf a few years ago on the basics of logic programming and
constraint propagation, and demoed a (poorly-coded) solver interface.[1]

[0][https://norvig.com/sudoku.html](https://norvig.com/sudoku.html)

[1][https://m.youtube.com/watch?v=f5Bi6_GOIB8](https://m.youtube.com/watch?v=f5Bi6_GOIB8)

~~~
triska
In my opinion, a key attraction of Prolog is the regularity of its syntax and
the relation between its syntax and semantics.

I mean not only homoiconicity, but something beyond that.

For example, one of the first questions of Prolog beginners is usually: "Why
does my predicate _fail_ for a specific query?"

Prolog allows us to mechanically find and give a minimal explanation _why_ a
query fails. We can do this by systematically _generalizing_ the program and
show a minimal fragment that _still_ fails. We can mechanically generalize a
program by using logic variables instead of concrete values, and by removing
goals (i.e., constraints) from conjunctions.

A key attraction of Prolog is that if you take any pure program, and replace
any of the goals by true/0, you still get a valid _and more general_ (or at
least equally general) program.

For example, take the Prolog program:

    
    
        my_list(Ls) :-
            Ls = [a,b|Rs],
            Ls = [a,c|Rs].
    

In this example, I am using trivial constraints (only (=)/2), and the mistake
is quite obvious. But the point remains: We can remove and generalize any and
all goals and give explanations why a query fails.

For example, why does the most general query, i.e., ?- my_list(Ls)., fail?

A possible explanation could like like this:

    
    
        my_list(Ls) :-
            Ls = [a,b|Rs],
            * Ls = [a,c|Rs].
    

here, I have used ( * )/1 to "generalize away" a goal, using a suitable
definition of ( * )/1 like:

    
    
        :- op(920,fy, *). *_.
    

The fact that the query then _succeeds_ tells us that the second goal is, in a
sense, a _reason_ for the failure.

No other language I have seen gives comparable guarantees and ways of
debugging. For example, suppose I remove a single form in a miniKanren
program. The result may no longer even constitute a valid program, and even if
it does, I am not aware of any meaningful general property that can be said
about the remaining program.

The ability to systematically generate explanations like this seems to be
quite unique to logic programming languages like Prolog.

------
fjfaase
I personally think it is a little overkill to use Prolog to solve sudokus, as
there is a simple mapping of sudokus to exact covers. The resulting exact
covers are rather small and can usually be solved within a fraction of a
second using Knuth's Algorithm X, even for hard sudokus.
[http://www.iwriteiam.nl/D1009.html#9](http://www.iwriteiam.nl/D1009.html#9)

------
tmaly
I would love to see Prolog implemented in hardware like the Lisp machines.

~~~
tom_mellior
It's been done, though only in an academic setting:

[https://www2.eecs.berkeley.edu/Pubs/TechRpts/1987/5991.html](https://www2.eecs.berkeley.edu/Pubs/TechRpts/1987/5991.html)

[https://www.researchgate.net/publication/242056713_Special_o...](https://www.researchgate.net/publication/242056713_Special_or_general-
purpose_hardware_for_Prolog_A_comparison) (sorry to link to
ResearchGateKeeper, though somehow by closing the tab, deleting cookies, and
opening the tab I managed to get it to give me the PDF without needing to log
in, lol)

[https://www.sciencedirect.com/science/article/pii/S074310669...](https://www.sciencedirect.com/science/article/pii/S0743106696889813)
(Elsevier, urgh)

I'm sure a bit of web searching will turn up more. It looks like having
special purpose hardware for Prolog is useful, which I don't find too
surprising. But if you make hardware, you need someone to give you money for
it.

As an aside, I wonder now much of the use of Lisp machines was non-academic. I
know that there were actual commercial companies selling them, but where those
mostly university spinoffs selling back to universities?

~~~
lispm
> It's been done, though only in an academic setting

See the Melcom PSI computer from Mitsubishi.

[http://museum.ipsj.or.jp/en/computer/other/0009.html](http://museum.ipsj.or.jp/en/computer/other/0009.html)

> As an aside, I wonder now much of the use of Lisp machines was non-academic.
> I know that there were actual commercial companies selling them, but where
> those mostly university spinoffs selling back to universities?

I would think that roughly 10000 hardware Lisp machines were built (incl.
embedded boards). Quite a lot went into research, but not just academic
research, but also company R&D.

Some machines were built as deployment machines and were used by military,
government agencies (like NASA) and also commercial.

Notable were for example the graphics systems by Symbolics which were widely
used in high-end graphics for TV and animation studios. A lot of computer
animated logos you were seeing in TV in the mid/end 80s were done with them.

Examples:

[https://www.youtube.com/watch?v=Cwer_xKrmI4](https://www.youtube.com/watch?v=Cwer_xKrmI4)

[https://www.youtube.com/watch?v=n21FSKDjObg](https://www.youtube.com/watch?v=n21FSKDjObg)

But there were a bunch of other early deployed applications on Lisp Machines:
3d cad (iCAD), American Express Authorizer (credit authorization expert
system), scheduling for the Hubble Space Station, scheduling for power plant
operations, seat allocation optimization for swissair, early military troop
virtual reality training system, satellite image processing, ...

------
carapace
Triska on the front page! Sweet.

It was in large part thanks to "The Power of Prolog" that I was able to figure
out a nice solution to the "Zebra" puzzle:

    
    
        /*
    
        Via https://www.metalevel.at/prolog/puzzles
    
        The "Zebra Puzzle" https://en.wikipedia.org/wiki/Zebra_Puzzle
    
        -    There are five houses.
        -    The Englishman lives in the red house.
        -    The Spaniard owns the dog.
        -    Coffee is drunk in the green house.
        -    The Ukrainian drinks tea.
        -    The green house is immediately to the right of the ivory house.
        -    The Old Gold smoker owns snails.
        -    Kools are smoked in the yellow house.
        -    Milk is drunk in the middle house.
        -    The Norwegian lives in the first house.
        -    The man who smokes Chesterfields lives in the house next to the man with the fox.
        -    Kools are smoked in the house next to the house where the horse is kept.
        -    The Lucky Strike smoker drinks orange juice.
        -    The Japanese smokes Parliaments.
        -    The Norwegian lives next to the blue house.
    
        Now, who drinks water? Who owns the zebra?
    
        In the interest of clarity, it must be added that each of the five houses is painted
        a different color, and their inhabitants are of different national
        extractions, own different pets, drink different beverages and smoke
        different brands of American cigarets [sic].
        One other thing: in statement 6, right means your right.
    
        */
        :- use_module(library(clpfd)).
    
    
        house_next_to(H0, H1) :- abs(H0 - H1) #= 1.
        /* Thanks https://www.metalevel.at/prolog/puzzles */
    
        puzzle(Vars) :- 
     
         Vars = [GreenHouse, RedHouse, IvoryHouse, YellowHouse, BlueHouse,
          Englishman, Spaniard, Ukrainian, Norwegian, Japanese,
          Dog, Snails, Fox, Horse, Zebra,
          Coffee, Tea, Milk, OrangeJuice, Water,
          OldGold, Kools, Chesterfields, LuckyStrike, Parliaments
         ],
     
         Vars ins 1..5,
     
         all_distinct([GreenHouse, RedHouse, IvoryHouse, YellowHouse, BlueHouse]),
         all_distinct([Englishman, Spaniard, Ukrainian, Norwegian, Japanese]),
         all_distinct([Dog, Snails, Fox, Horse, Zebra]),
         all_distinct([Coffee, Tea, Milk, OrangeJuice, Water]),
         all_distinct([OldGold, Kools, Chesterfields, LuckyStrike, Parliaments]),
     
         Englishman #= RedHouse,
         Spaniard #= Dog,
         Coffee #= GreenHouse,
         Ukrainian #= Tea,
         GreenHouse #= 1 + IvoryHouse,
         OldGold #= Snails,
         Kools #= YellowHouse,
         Milk #= 3,
         Norwegian #= 1,
         house_next_to(Chesterfields, Fox),
         house_next_to(Kools, Horse),
         LuckyStrike #= OrangeJuice,
         Japanese #= Parliaments,
         house_next_to(Norwegian, BlueHouse).
    
    
        /*
    
        Then to print out the solution after loading the program above use:
    
        puzzle([
         GreenHouse, RedHouse, IvoryHouse, YellowHouse, BlueHouse,
         Englishman, Spaniard, Ukrainian, Norwegian, Japanese,
         Dog, Snails, Fox, Horse, Zebra,
         Coffee, Tea, Milk, OrangeJuice, Water,
         OldGold, Kools, Chesterfields, LuckyStrike, Parliaments]),
        label([
         GreenHouse, RedHouse, IvoryHouse, YellowHouse, BlueHouse,
         Englishman, Spaniard, Ukrainian, Norwegian, Japanese,
         Dog, Snails, Fox, Horse, Zebra,
         Coffee, Tea, Milk, OrangeJuice, Water,
         OldGold, Kools, Chesterfields, LuckyStrike, Parliaments]).
    
        */
    
    
    

See also "Boring Sudoku":

    
    
        /*
        Boring Prolog Sudoku.
        */
        :- use_module(library(clpfd)).
    
    
        puzzle(Vars) :-
    
            Vars = [
                A11, A12, A13, A14, A15, A16, A17, A18, A19,
                A21, A22, A23, A24, A25, A26, A27, A28, A29,
                A31, A32, A33, A34, A35, A36, A37, A38, A39,
                A41, A42, A43, A44, A45, A46, A47, A48, A49,
                A51, A52, A53, A54, A55, A56, A57, A58, A59,
                A61, A62, A63, A64, A65, A66, A67, A68, A69,
                A71, A72, A73, A74, A75, A76, A77, A78, A79,
                A81, A82, A83, A84, A85, A86, A87, A88, A89,
                A91, A92, A93, A94, A95, A96, A97, A98, A99],
    
            Vars ins 1..9,
    
            all_distinct([A11, A12, A13, A14, A15, A16, A17, A18, A19]),
            all_distinct([A21, A22, A23, A24, A25, A26, A27, A28, A29]),
            all_distinct([A31, A32, A33, A34, A35, A36, A37, A38, A39]),
            all_distinct([A41, A42, A43, A44, A45, A46, A47, A48, A49]),
            all_distinct([A51, A52, A53, A54, A55, A56, A57, A58, A59]),
            all_distinct([A61, A62, A63, A64, A65, A66, A67, A68, A69]),
            all_distinct([A71, A72, A73, A74, A75, A76, A77, A78, A79]),
            all_distinct([A81, A82, A83, A84, A85, A86, A87, A88, A89]),
            all_distinct([A91, A92, A93, A94, A95, A96, A97, A98, A99]),
    
            all_distinct([A11, A21, A31, A41, A51, A61, A71, A81, A91]),
            all_distinct([A12, A22, A32, A42, A52, A62, A72, A82, A92]),
            all_distinct([A13, A23, A33, A43, A53, A63, A73, A83, A93]),
            all_distinct([A14, A24, A34, A44, A54, A64, A74, A84, A94]),
            all_distinct([A15, A25, A35, A45, A55, A65, A75, A85, A95]),
            all_distinct([A16, A26, A36, A46, A56, A66, A76, A86, A96]),
            all_distinct([A17, A27, A37, A47, A57, A67, A77, A87, A97]),
            all_distinct([A18, A28, A38, A48, A58, A68, A78, A88, A98]),
            all_distinct([A19, A29, A39, A49, A59, A69, A79, A89, A99]),
    
            all_distinct([A11, A21, A31, A12, A22, A32, A13, A23, A33]),
            all_distinct([A41, A51, A61, A42, A52, A62, A43, A53, A63]),
            all_distinct([A71, A81, A91, A72, A82, A92, A73, A83, A93]),
            all_distinct([A14, A24, A34, A15, A25, A35, A16, A26, A36]),
            all_distinct([A44, A54, A64, A45, A55, A65, A46, A56, A66]),
            all_distinct([A74, A84, A94, A75, A85, A95, A76, A86, A96]),
            all_distinct([A17, A27, A37, A18, A28, A38, A19, A29, A39]),
            all_distinct([A47, A57, A67, A48, A58, A68, A49, A59, A69]),
            all_distinct([A77, A87, A97, A78, A88, A98, A79, A89, A99]).

~~~
todd8
For comparison, here is my own solution in Python 3 to the Zebra Puzzle. To me
this solution seems sorter and easier to understand:

    
    
      from itertools import permutations as perms
      houses = range(1,6)                                # houses numbered 1,2,3,4,5
      for Englishman, Spaniard, Ukrainian, Norwegian, Japanese in perms(houses):
          if Norwegian != 1: continue                                      # rule 10
          for red, green, ivory, yellow, blue in perms(houses):
              if Englishman != red: continue                               # rule 2
              if green != ivory + 1: continue                              # rule 6
              if Norwegian not in [blue-1,blue+1]: continue                # rule 15
              for coffee, tea, milk, oj, water in perms(houses):
                  if coffee != green: continue                             # rule 4
                  if Ukrainian != tea: continue                            # rule 5
                  if milk != 3: continue                                   # rule 9
                  for oldgold, kools, chesterfields, luckys, parliaments in perms(houses):
                      if kools != yellow: continue                         # rule 8
                      if luckys != oj: continue                            # rule 13
                      if Japanese != parliaments: continue                 # rule 14
                      for dog, snails, fox, horse, zebra in perms(houses):
                          if Spaniard != dog: continue                     # rule 3
                          if oldgold != snails: continue                   # rule 7
                          if chesterfields not in [fox-1,fox+1]: continue  # rule 11
                          if kools not in [horse-1, horse+1]: continue     # rule 12
    
                          person = {Englishman: 'Englishman', Spaniard: 'Spainard',
                                    Ukrainian: 'Ukrainian', Norwegian: 'Norwegian', Japanese: 'Japanese'}
    
                          print(f'{person[water]} drinks water')
                          print(f'{person[zebra]} owns the zebra')

~~~
anewhnaccount2
Enumerating all solutions and checking will always be the simplest approach
for find the set of inputs returning true for a decision problem. However, a
constraint solver will not necessarily have to enumerate all solutions.

~~~
todd8
Yes it would take a long time to do it that way since there would be roughly
25 billion solutions to evaluate. _My Python program doesn’t do this and runs
very fast_. Elapsed time for this program is approximately 0.03 seconds on my
2013 desktop and it finds all possible solution (in this case one).

Normally, inner loops are executed more times than outer loops, but in this
program the bodies of the inner loops are skipped (because of the continue
statements) when constraints fail in the the outer loops. The five loop bodies
are executed a total of 120, 2880, 1440, 1440, 960 times respectively (I put
counters in each loop to determine this, all counters were initialized before
any of the loops begin).

I wrote this program (for a different version of this same problem) while
waiting for my daughter to come downstairs to be driven to school one morning
around five years ago. (See
[https://news.ycombinator.com/item?id=7734998](https://news.ycombinator.com/item?id=7734998))

