Logic programming languages are not procedural or functional
class Student extends Person { int year; float gpa; string major; };
can be represented, among others, as
/* student(_), person(_), inYear(_,_), hasGpa(_,_), inMajor(_,_), ... */ person(X) :- student(X). student(jane). /* jane = new Student(); */ inYear(jane, 3). /* jane.year = 3; */ hasGpa(jane, 3.94). /* jane.gpa = 3.94; */ inMajor(jane, 'cs'). /* jane.major = "cs"; */
Separate logic from control:
Created in 1970s
Roots in first-order logic
Declarative: program logic is expressed in terms of relations, facts and rules
Name is abbreviation for programmation en logique (French for programming in logic)
Useful for rule-based logic queries (db searching), voice control systems, template filling, theorem proving, expert systems, natural language processing
Programs composed of:
Example of facts:
likes(eve, pie). likes(al, eve). likes(eve, tom). likes(eve, eve). food(pie). food(apple). person(tom).
Constants: eve
, tom
, ...
Predicates: likes(...)
, person(...)
, ...
Facts:
likes(eve, pie). likes(al, eve). likes(eve, tom). likes(eve, eve). food(pie). food(apple). person(tom).
What if you want to ask the same question often? Add a rule to the database.
rule1 :- likes(eve, V), person(V).
Query:
?- rule1. true.
Facts and Rules:
likes(eve, pie). likes(al, eve). likes(eve, tom). likes(eve, eve). food(pie). food(apple). person(tom). rule1 :- likes(eve, V), person(V). rule2(V) :- likes(eve, V), person(V).
Query:
?- rule2(H). H = tom ; false.
Note: rule1
and rule2
are just like any other predicate!
Facts:
male(paul). male(dan). male(jon). male(samuel). male(linus). male(tobin). female(linda). female(cheryl). female(cindy). female(cora). female(felicity). female(ada). female(edith). female(luna). female(twig). female(thunder). mother(linda,cindy). mother(cheryl,jon). mother(cindy,cora). mother(cindy,samuel). mother(cindy,linus). mother(cindy,felicity). mother(cindy,ada). mother(cindy,edith). mother(cindy,tobin). father(paul,cindy). father(dan,jon). father(jon,cora). father(jon,samuel). father(jon,linus). father(jon,felicity). father(jon,ada). father(jon,edith). father(jon,tobin). pet(luna). pet(twig). pet(thunder).
Reminder: the atoms above are meaningless to Prolog; only has meaning to us.
Rules:
parent(M,C) :- mother(M,C). parent(F,C) :- father(F,C). parents(M,F,C) :- mother(M,C), father(F,C). /* implied */ grandparent(X,Z) :- parent(X,Y), parent(Y,Z). /* Y is unified to all possible values that make */ /* RHS true, but Y is not reported */
What about the following?
sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y).
We'll come back to this soon...
In Prolog, a horn clause
\[c \leftarrow h_1, h_2, h_3, ..., h_n\]
is written as
c :- h1, h2, ..., hn.
Horn Clause is a Clause
Consequent (c
) is a Goal or a Head
Antecedents (h1
,h2
,...) are Subgoals or Tail
Horn Clause with Head but No Tail is a Fact:
male(jon).
Horn Clause with Head and Tail is a Rule:
father(jon, linus) :- male(jon), parents(M, jon, linus).
Horn Clause with No Head but Tail is executed right away:
:- male(M), writeln(M).
Variables may appear in the antecedents and consequent of a Horn Clause
c(X1, ..., Xn) :- h(X1, ..., Xn, Y1, ..., Yk).
For all values of X1, ..., Xn
, the formula c(X1, ..., Xn)
is true if there exist values of Y1, ..., Yk
such that the formula h(X1, ..., Xn, Y1, ..., Yk)
is true.
Call Y1, ..., Yk
auxiliary variables. Their values will be bound to make consequent true, but they are not reported by Prolog, because they don't appear in the consequent.
Prolog program consists of facts and rules.
Rules like
sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y). /* X is the sister of Y, if X is female and there are M and F who */ /* are X's parents as well as Y's parents. */
correspond to logical formulas
\[\begin{eqnarray} \forall X,Y . \mathit{sister\_of}(X,Y) \leftarrow \exists M,F\ .\ & ( & \\ & & \mathit{female}(X)\ \wedge \\ & & \wedge\ \mathit{parents}(M,F,X)\ \wedge \\ & & \wedge\ \mathit{parents}(M,F,Y) \\ & ) & \end{eqnarray}\]
Note: variables not in head are existentially quantified
A query is a conjunction of atoms to be proven
Prolog system will attempt to use resolution to find a value for X
such that the query is true.
?- sister_of(X, samuel).
Queries:
sister_of(X, samuel). sister_of(X, Y). /* what happens here? */ sister_of(X, edith). /* or here? */
We'll come back to this example soon...
Prolog uses pattern matching on text of facts and rules
Can apply with
Forward chaining
Backward chaining
Facts/Rules | a:-b. b:-c. c:-d. b. |
Query | ?- a. |
We can control Prolog's backtracking behavior by using the cut operator (!
).
Here is an example of using the cut operator. (SWISH)
A subtle note is that a cut operator prevents Prolog from backtracking, but only for the current clause.
A small tweak to the previous program illustrates this.
a(X,Y) :- c(X), d(Y). b(X,Y) :- c(X), !, d(Y). e(X,Y) :- c(X), b(X,Y). c(1). c(2). d(1). d(2). d(3).
Querying e(Q,R)
will cause Prolog to find all X
such that C(X)
is true, then Prolog tries to prove b(X,Y)
.
The b(X,Y)
goal tries to prove c(X)
(already true for X
!), crosses the cut, then proves d(Y)
.
Once all Y
have been found, Prolog cannot backtrack across the cut, so the b(X,Y)
goal is finished, and Prolog goes back to proving e(X,Y)
using other X
.
Prolog Program
Now, let's go back to the sister_of
rule
edith
is a sister of edith
? Oops!
Prolog searches using backtracking
sister_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y). ?- sister_of(X, edith).
X
that makes female(X)
true.M
and F
to make parents(M,F,X)
true for that X
.Y
to make parents(M,F,Y)
true for those M
and F
.This algorithm is recursive; each find works on a new "copy" of the facts+rules. Eventually, each find must be resolved by appealing to facts.
The process is called backward chaining.
Variables are local: every time rule is used, new names for X
,Y
,M
,F
.
Rule ordering (from first to last) used in search
Unification requires all instances of the same variable in a rule to get the same value
Unification does not require differently named variables to get different values: sister_of(edith, edith)
All rules searched if requested by successive typing of ;
With all this, how do we fix sister_of
?
Need to modify sister_of
to be
sis_of(X,Y) :- female(X), parents(M,F,X), parents(M,F,Y), \+(X==Y). /* last subgoal disallows X,Y to have same value */ ?- sis_of(X, edith). X = cora ; X = felicity ; X = ada.
Note:
=
is the unification predicate, meaning unifies withX=edith
is true
, X=Y
is true
)==
is the identity predicate, meaning same in valueX==X
is true
, but X==Y
is false
)\+(P)
succeeds when P
fails
Called negation by failure and defined as
not(X) :- X, !, fail. not(_).
which means:
X
succeeds in first rule, then the rule is forced to fail by the last subgoal (fail
). We cannot backtrack over the cut (!
) in the first rule, and the cut prevents us from accessing the second rule.X
fails, then the second rule succeeds, because _
(or "don't care") unifies with anything.mnemonic: +
refers to provable and the backslash (\
) is normally used to indicate negation in Prolog
\+/1
]Suppose we have the following predicate proposition that we wish to express in Prolog
\[\forall A \biggl[ \neg s(A) \rightarrow \Bigl( \neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B) \bigr]\Bigr)\biggr]\]
where
\[s : \text{student} \quad d : \text{dorm resident} \quad t : \text{takes} \quad c : \text{class}\]
Convert first order predicate proposition to clausal form and then to Prolog syntax
\[\forall A \biggl[ \neg s(A) \rightarrow \Bigl( \neg d(A) \land \neg \exists B \bigl[t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]
Eliminate implication and equivalence
\[\forall A \biggl[ s(A) \lor \Bigl(\neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]
\[\forall A \biggl[ s(A) \lor \Bigl(\neg d(A) \land \neg \exists B \bigl[ t(A,B) \land c(B)\bigr]\Bigr)\biggr]\]
Move negation inward so only negated items are individual terms
\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg\bigl(t(A,B) \land c(B)\bigr)\bigr]\Bigr)\biggr]\]
\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg t(A,B) \lor \neg c(B)\bigr]\Bigr)\biggr]\]
\[\forall A\biggl[s(A) \lor \Bigl(\neg d(A) \land \forall B\bigl[\neg t(A,B) \lor \neg c(B)\bigr]\Bigr)\biggr]\]
Move quantifiers to the outside and adopt convention that all variables are universally quantified
\[s(A) \lor \Bigl(\neg d(A) \land \bigl(\neg t(A,B) \lor \neg c(B)\bigr)\Bigr)\]
Use distributive, associative, and commutative properties to put in conjunctive normal form, where \(\land\) and \(\lor\) operators are nested no more than two deep with \(\land\) on the outside and \(\lor\) on the inside.
\[\bigl[s(A) \lor \neg d(A)\bigr] \, \land \, \bigl[s(A) \lor \neg t(A,B) \lor \neg c(B)\bigr]\]
Finally, convert each logical clause to Prolog fact or rule
Within each clause, move negated terms to right and non-negated terms to left (already done for this example)
\[\bigl[s(A) \lor \neg d(A)\bigr] \, \land \, \bigl[s(A) \lor \neg t(A,B) \lor \neg c(B)\bigr]\]
Recast disjunctions as implications
\[(s(A) \leftarrow \neg(\neg d(A))) \land (s(A) \leftarrow \neg(\neg t(A,B) \lor \neg c(B)))\] \[(s(A) \leftarrow d(A)) \land (s(A) \leftarrow (t(A,B) \land c(B)))\]
Translate to Prolog
student(A) :- dormResident(A). student(A) :- takes(A,B), class(B).
Prolog has four terms
See ANTLR grammar on GitHub
Atoms are one of the following
foo
, foo_Bar12
'foo'
, 'wacka wacka!'
:-
, @=
Some atoms have special meaning
Numbers are usually integers, but can be floats (1
, -2
, 3.141
)
Variables are defined as follows
Notes:
_
) has special meaning: "don't care"_
can have a different value (ex: likes(_, _).
matches both facts below), where any other variable (ex: A
or _foo
) must be the same within a fact or rule (ex: likes(A, A).
matches only likes(eve, eve).
)Example facts
likes(al, eve). likes(eve, eve).
Complex terms
father(jon,edith)
Running a program consists of asking the interpreter a question
Questions asked by stating a theorem (assert a predicate)
The interpreter tries to prove true
or false
Prolog prints the values of variables that make predicate true
(except variables starting with underscore)
Programs are stored in files and consulted (kind of like importing)
?- consult('familytree.pl'). % familytree.pl compiled 0.00 sec, 19 clauses true. ?- ['familytree.pl']. /* alternative approach */ ?- ['file1.pl', 'file2.pl']. /* can consult many files */
Notice that consult
is a functor and 'familytree.pl'
is an atom!
You can dynamically add new facts and rules to the database using assert
, remove with retract
?- assert( father(jon) ). true. ?- assert( male(X) :- father(X) ). true. ?- male(X). X = jon.
Note: the double parens are necessary for asserts with rules containing conjunctions
?- assert( father_of(jon, dan) ). /* true. */ ?- assert( father_of(linus, jon) ). /* true. */ ?- assert( grandfather_of(X,Z) :- father_of(X,Y), father_of(Y,Z) ). ERROR: assert/2: Uninstantiated argument expected, found father_of(_G4266,_G4267) (2-nd argument) ?- assert(( grandfather_of(X,Z) :- father_of(X,Y), father_of(Y,Z) )). true.
Lists are a series of terms separated by commas and enclosed in square brackets
[] [the, giraffe, dreams] [_, X, Y] ['sarlac pit', father_of(luke,'Nooo'), [11.99,parsecs], shotfirst(han)]
Lists can be written as
[Head | TailList] [First, Second | TailList] [First, Second | [Third, Fourth | TailList]]
For example
?- assert([1,2,3]). /* true. */ ?- [X|Y]. /* X = 1, Y = [2,3]. */ ?- [X,Y|Z]. /* X = 1, Y = 2, Z = [3]. */ ?- [X,Y,Z|A]. /* X = 1, Y = 2, Z = 3, A = []. */
Let's write a set of rules to prove that an element is a member of a list
?- member_of(chewie, [han,leia,luke,'c-3po','r2-d2',chewie,lando]). true.
How do we define this? (hint: use recursion!)
Recall a list can be written [H|T]
, so X
is a member of the list if...
Let's write a set of rules to prove that an element is a member of a list
?- member_of(chewie, [han,leia,luke,'c-3po','r2-d2',chewie,lando]). true.
How do we define this? (hint: use recursion!)
Recall a list can be written [H|T]
, so X
is a member of the list if...
Base case:
member_of(X, [X|_]).
Inductive step / rule:
member_of(X, [_|Y]) :- member_of(X,Y).
Now let's write a set of rules that will append (concat) one list (second) to the end of another (first)
?- append( [1,2], [3,4], [1,2,3,4] ). /* true. */
We can use recursion again...
Now let's write a set of rules that will append (concat) one list (second) to the end of another (first)
?- append( [1,2], [3,4], [1,2,3,4] ). /* true. */
We can use recursion again...
Base case:
/* appending Y to an empty list results in Y */ append([], Y, Y).
Inductive step / rule:
append([H | X], Y, [H | Z]) :- append(X, Y, Z).
If the list Z
is Y
appended to X
, then Y
appended to the list [H|X]
(which is one element larger) is [H|Z]
Let's look at the classic recursive problem of computing factorial
\[\begin{eqnarray} \mathit{factorial}(n) & = & \begin{cases} 1 & \text{if } n = 0 \\ n \cdot \textit{factorial}(n - 1) & \text{otherwise} \end{cases} \end{eqnarray}\]
In Prolog
factorial(0, 1). factorial(N, F) :- N>0, M is N-1, factorial(M, S), F is N*S.
The is
term is actually an infix functor (M is N-1
is same as is(M, N-1)
) for temporary instantiation ("local variable").
See documentation (link) for more details.
Now, let's test our factorial
rules using Prolog's debug trace
?- assert(factorial(0,1)). true. ?- assert((factorial(N,F) :- N>0, M is N-1, factorial(M,S), F is N*S)). true. ?- factorial(3,6). true. ?- factorial(4,X). X = 24 ; false. ?- trace(factorial). % factorial/2: [call,redo,exit,fail] true. [debug] ?- factorial(4,X). T Call: (6) factorial(4, _G1692) T Call: (7) factorial(3, _G1800) T Call: (8) factorial(2, _G1803) T Call: (9) factorial(1, _G1806) T Call: (10) factorial(0, _G1809) T Exit: (10) factorial(0, 1) T Exit: (9) factorial(1, 1) T Exit: (8) factorial(2, 2) T Exit: (7) factorial(3, 6) T Exit: (6) factorial(4, 24) X = 24 ; T Redo: (10) factorial(0, _G1809) T Fail: (10) factorial(0, _G1809) T Fail: (9) factorial(1, _G1806) T Fail: (8) factorial(2, _G1803) T Fail: (7) factorial(3, _G1800) T Fail: (6) factorial(4, _G1692) false. [debug] ?- nodebug. true. ?- factorial(4,X). X = 24 ; false.
Note: factorial(0, _G1809)
redos on line 29, because Prolog has a rule (line 3) that might prove true, but Prolog fails on line 30, because 0>0
is not true.
Here is a question about Prolog that has an important but subtle answer:
Why does this work
factorial(0,1). factorial(N,F) :- N>0, M is N-1, factorial(M,S), F is N*S. ?- factorial(3,6). true.
but this does NOT work?
factorial2(0,1). factorial2(N,F) :- N>0, factorial2(N-1,S), F is N*S. ?- factorial(3,6). false.
Similar to how is
is a functor, -
is also a functor.
Therefore, N-1
is actually -(N,1)
(a complex term), and a complex term does not unify with a number!
?- (1-1) == 0. false.
In other words, the complex term 3-1-1-1
(which really is -(-(-(3,1),1),1)
) does not unify with 0
in the fact factorial2(0,1)
, so Prolog cannot prove the query.
You must force Prolog to evaluate the -
by instantiating / binding a variable with is
or using the =:=
comparison
?- X is 1-1, X == 0. X = 0. ?- (1-1) =:= 0. true.
factorial(0,1). factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1. factorial2(0,1). factorial2(N,F) :- N>0, factorial2(N-1,S), F is N*S.
Here is the trace debug of the two calls
Two more important notes about Prolog
First, Prolog is not purely declarative, so reordering the actions in the Prolog program changes the path Prolog uses
Searching and backtracking have deterministic imperative semantics
Second, the negation as failure term (\+
) is not equivalent to the logical not (\(\neg\))
But, if we instead wrote
What happened?
The two are logically equivalent, but Prolog attempts to prove subgoals left-to-right.
enjoys(vincent, X) :- burger(X), \+ big_kahuna_burger(X). enjoys(vincent, X) :- \+ big_kahuna_burger(X), burger(X).
Using the new rule, Prolog first checks whether
\+ big_kahuna_burger(X)
holds, which means that it must check whether big_kahuna_burger(X)
fails.
But this succeeds, because the database contains the fact big_kahuna_burger(b)
.
So \+ big_kahuna_burger(X)
fails, and Prolog backtracks to make another attempt.
Since this subgoal is the first and it failed, Prolog concludes that it cannot prove the query to be true.
Accumulators are commonly implemented pattern used to increase readability, maintainability, and efficiency of recursive rules
For example, we can rewrite the factorial rule
factorial(0,1). factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1.
to use an accumulator
factorial3(0,F,F). factorial3(N,A,F) :- N>0, A1 is N*A, N1 is N-1, factorial3(N1,A1,F). factorial3(N,F) :- factorial3(N,1,F). /* helper "kick-off" rule */
The second parameter is called an accumulating parameter, and it will "accumulate" the solution while it recurses.
The accumulator version uses tail recursion, which can be heavily optimized by the system
factorial(0,1). factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F is N*F1. factorial3(0,F,F). factorial3(N,A,F) :- N>0, A1 is N*A, N1 is N-1, factorial3(N1,A1,F). factorial3(N,F) :- factorial3(N,1,F). /* helper "kick-off" rule */
?- factorial(4,X). T [10] Call: factorial(4, _13006) T [19] Call: factorial(3, _14186) T [28] Call: factorial(2, _15066) T [37] Call: factorial(1, _480) T [46] Call: factorial(0, _1218) T [46] Exit: factorial(0, 1) T [37] Exit: factorial(1, 1) T [28] Exit: factorial(2, 2) T [19] Exit: factorial(3, 6) T [10] Exit: factorial(4, 24) X = 24 ; T [10] Redo: factorial(4, 24) T [19] Redo: factorial(3, 6) T [28] Redo: factorial(2, 2) T [37] Redo: factorial(1, 1) T [46] Redo: factorial(0, 1) T [46] Fail: factorial(0, _1218) T [37] Fail: factorial(1, _480) T [28] Fail: factorial(2, _330) T [19] Fail: factorial(3, _184) T [10] Fail: factorial(4, _18) false. |
?- factorial3(4,X). T [10] Call: factorial3(4, _6014) T [19] Call: factorial3(4, 1, _6014) T [28] Call: factorial3(3, 4, _6014) T [37] Call: factorial3(2, 12, _6014) T [46] Call: factorial3(1, 24, _6014) T [55] Call: factorial3(0, 24, _6014) T [55] Exit: factorial3(0, 24, 24) T [46] Exit: factorial3(1, 24, 24) T [37] Exit: factorial3(2, 12, 24) T [28] Exit: factorial3(3, 4, 24) T [19] Exit: factorial3(4, 1, 24) T [10] Exit: factorial3(4, 24) X = 24 ; T [10] Redo: factorial3(4, 24) T [19] Redo: factorial3(4, 1, 24) T [28] Redo: factorial3(3, 4, 24) T [37] Redo: factorial3(2, 12, 24) T [46] Redo: factorial3(1, 24, 24) T [55] Redo: factorial3(0, 24, 24) T [55] Fail: factorial3(0, 24, _18) T [46] Fail: factorial3(1, 24, _18) T [37] Fail: factorial3(2, 12, _18) T [28] Fail: factorial3(3, 4, _18) T [19] Fail: factorial3(4, 1, _18) T [10] Fail: factorial3(4, _18) false. |
Reversing a list is another example of using an accumulator, where we prepend to the accumulator the head of the list
list: [a,b,c,d] accumulator: [] list: [b,c,d] accumulator: [a] list: [c,d] accumulator: [b,a] list: [d] accumulator: [c,b,a] list: [] accumulator: [d,c,b,a]
Naive implementation
/* H: Head, T: Tail, R: list Reversed, RevT: T Reversed */ rev([],[]). rev([H|T], R) :- rev(T, RevT), append(RevT, [H], R).
Using accumulator
/* A: Accumulator, H,T,R: same as above */ rev2([], A, A). rev2([H|T], A, R) :- rev2(T, [H|A], R). rev2(L, R) :- rev2(L, [], R). % helper "kick-off" rule
Last rule is a helper that initializes the accumulator for us
The naive rev
version takes 90 steps to reverse a list of 8 elements, but accumulator rev2
takes about 20.
/* H: Head, T: Tail, R: list Reversed, RevT: T Reversed */ rev([],[]). rev([H|T], R) :- rev(T, RevT), append(RevT, [H], R). rev2([], A, A). rev2([H|T], A, R) :- rev2(T, [H|A], R). rev2(L, R) :- rev2(L, [], R). % helper "kick-off" rule
What can the monkey do?
Can the monkey get the banana? If so, how?
The world of the room, the monkey, the box, the banana, and the actions the monkey can perform can be represented by a state.
The state is a Prolog structure.
What should be in the state?
State: state(H,V,B,HB)
atdoor
, atwindow
, middle
onfloor
, onbox
atdoor
, atwindow
, middle
hasnot
, has
Goal: state(_,_,_,has)
Actions change state (state(H,V,B,HB)
) of monkey
How can the state change?
What rules must be used in changing state?
Possible actions (act(...)
):
grasp
), but monkey must be near bananaclimb
), but monkey must be near boxpush
) from one position (H1
) to another (H2
)walk
) from one position (H1
) to (H2
)Let's define actions as: act(PrevState, Action, NextState)
How would we implement this?
/* reminder: state(H, V, B, HB) H,B horizontal positions: atdoor, atwindow, middle V vertical positions: onfloor, onbox HB has banana or has not: has, hasnot */ /* goal: reach state where monkey has banana */ canget( state(_, _, _, has) ). /* canget(State1) is true if we can transition from State1 to State2 via Action and reach banana from State2 */ canget(State1) :- act(State1, Action, State2), canget(State2). /* initial starting condition */ go :- canget( state(atdoor, onfloor, atwindow, hasnot) ). /* Action: grasp, climb, push(H1, H2), walk(H1, H2) */ act(PrevState, Action, NextState) :- ...
run with ?- go.
Executing the Prolog program returns true.
, but it does not tell how. How do we improve this?
Furthermore, it returns many, many true
s. Why?
Improved Prolog solution
Prolog is excellent at solving combinatorial tasks such as Sudoku!
1 | 8 | 4 | ||||||
2 | 4 | 5 | 6 | |||||
3 | 2 | 5 | ||||||
4 | 8 | 5 | ||||||
7 | 8 | 9 | 5 | |||||
6 | 2 | 3 | ||||||
8 | 1 | 7 | ||||||
1 | 2 | 3 | 8 | |||||
2 | 5 | 9 |
:- use_module(library(clpfd)). /* helps solve combinatorial problems */ /* defines: all_distinct/1 and ins/2 */ /* describe correct sudoku puzzle */ sudoku(Rows) :- /* sudoku puzzles are 9x9 grid: 9rows and 9cols */ length(Rows, 9), maplist(length_(9), Rows), /* all values in puzzle are 1--9 */ append(Rows, Vs), Vs ins 1..9, /* each row contains distinct values (1--9) */ maplist(all_distinct, Rows), /* each col contains distinct values (1--9) */ transpose(Rows, Columns), maplist(all_distinct, Columns), /* each block contains distinct values (1--9) */ Rows = [A,B,C,D,E,F,G,H,I], blocks(A, B, C), blocks(D, E, F), blocks(G, H, I). /* solve and then render puzzle */ solve_sudoku(Puzzle) :- sudoku(Puzzle), show_sudoku(Puzzle). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% HELPER FUNCTIONS length_(L, Ls) :- length(Ls, L). /* break 3rows into 3x3 blocks; each block contains distinct vals */ blocks([], [], []). blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :- all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]), blocks(Ns1, Ns2, Ns3). /* render puzzle */ show_sudoku(Puzzle) :- maplist(label, Puzzle), maplist(portray_clause, Puzzle). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PUZZLES problem(1, P) :- /* shokyuu */ P = [[1,_,_,8,_,4,_,_,_], [_,2,_,_,_,_,4,5,6], [_,_,3,2,_,5,_,_,_], [_,_,_,4,_,_,8,_,5], [7,8,9,_,5,_,_,_,_], [_,_,_,_,_,6,2,_,3], [8,_,1,_,_,_,7,_,_], [_,_,_,1,2,3,_,8,_], [2,_,5,_,_,_,_,_,9]]. problem(2, P) :- /* shokyuu */ P = [[_,_,2,_,3,_,1,_,_], [_,4,_,_,_,_,_,3,_], [1,_,5,_,_,_,_,8,2], [_,_,_,2,_,_,6,5,_], [9,_,_,_,8,7,_,_,3], [_,_,_,_,4,_,_,_,_], [8,_,_,_,7,_,_,_,4], [_,9,3,1,_,_,_,6,_], [_,_,7,_,6,_,5,_,_]]. problem(3, P) :- P = [[1,_,_,_,_,_,_,_,_], [_,_,2,7,4,_,_,_,_], [_,_,_,5,_,_,_,_,4], [_,3,_,_,_,_,_,_,_], [7,5,_,_,_,_,_,_,_], [_,_,_,_,_,9,6,_,_], [_,4,_,_,_,6,_,_,_], [_,_,_,_,_,_,_,7,1], [_,_,_,_,_,1,_,3,_]].