A straightforward translation of the definition of a median into Prolog rules.
First we define partition(Pivot,List,Smaller,Greater)
which is
true if List
is a list of integers, Pivot
is an element of List
, and Smaller
and Greater
are lists of the elements of List
that are smaller and
respectively greater
than Pivot
. The definition loops over List
by inspecting the head and recursing for the tail. Apart from the base
case, there are three clauses for the three cases of the head of the
current list being equal to, smaller or greater than the pivot element.
In the last two cases, we add the head to the appropriate list and
recurse.
Given partition
, the median
predicate is
simple: M
is a median of list L
if it is a
member of L
, and if it partitions L
into
two lists S
and G
of equal length.
Membership and list length functions are borrowed from the class notes.
member(X,[X|_]). |
2.
(a)
The key notion in any set implementation is set membership.
All other functions build on this basic concept. For part (a), we
define the setMember(E,S)
predicate identical to the
usual list member predicate. This works for sets with atomic elements.
Next, we define setValid(S)
which tests if the input
list is a valid set, i.e., if it does not have repeated elements. This
is easily expressed recursively by checking that the head of the list
is not a member of the tail, and that the tail itself is a valid list.
Finally ,we define set equality as we did for the Scheme solution: S1 == S2 iff S1 is a subset of S2 and S2 is a subset of S1. The subset relationship has a trivial recursive definition.
setMember(X,[X|_]). |
(b)
All we need to change for this part is the notion of set membership.
We now need
to allow sets as members also. We add another clause to setMember
which uses the setEq
predicate to compare set elements if
they
are sets themselves. Atomic elements get compared with the second
clause, while
list (set) elements use the first clause. The rest of the code is
unchanged
from part (a).
setMember(X,[X|_]). |
3.
This question is an exercise in constraint satisfaction: you need to search for a solution in a state space, subject to some constraints: a problem naturally suited to Prolog.
Express each known constraint in Prolog and use the conjunction of the constraints as the top-level goal for Prolog. There are two kinds of constraints: those for individual variables, and those for the whole list of variables.
Constraints on individual variables: M is known to be 1, O is 0, S
is either 8 or 9, N is E+1, and the rest (and N too) are all digits
between 0 and 9. We define a digit
predicate that lists
the single-digit integers, and then define a predicate for the
constraint on each variable. Each predicate is passed both the variable
and the whole solution, in case its value depends on other variables
(as with N).
Constraints on the whole list: all variables have to map to distinct
integers. We implement using the alldistinct
predicate,
which is identical to the setValid
predicate of Q.2. The
final constraint is that when variables are mapped, SEND + MORE =
MONEY.
Finally, soln
is a simple conjunction of these
constraints, with the individual constraints ordered before the
whole-list constraints. In general state space search, the formulation
and ordering of constraints can affect search time. E.g., if we defined
constraints in this order: all variables are single digits, they are
distinct, individual variables have such and such constraints, whole
solution has these constraints, then the search would take more time
since a number of variables satisfying earlier more relaxed constraints
would not satisfy the later stricter ones. In general, it helps to
consider more strict constraints first to limit the degree of freedom
for later constraints.
The unique solution is [D,E,M,N,O,R,S,Y] = [7, 5, 1, 6, 0, 8, 9, 2], 9567 + 1085 = 10652.
member(X,[X|_]). |
4.
As explained in the submission guidelines, this question is an exercise in showing the fuzzy and invertible nature of inputs and outputs in Prolog programs. You are lead from a straightforward algorithmic computation of the power set, to a declarative specification that can compute, check and search for <set, power set> combinations.
(a)
This part requires you to compute the power set of a given set. We
reuse the
power set algorithm used for Q.1 of Proj#1/Scheme, and translate the
original subsets
, extend
and distrib
functions into their equivalent Prolog predicates. The recursive list
processing
translates naturally.
The only additional nit is that we constrain the subset
function
to work with a known set and an unknown (to be computed) power set.
This is done
by asserting that Set
is not an unbound variable, while Powerset
is an unbound variable. The first cut (!
) forces Prolog
not to
search for other subsets
clauses once we know that we
have
to compute the power set from a given set, while the second cut tells
Prolog
that this subsets
is a 1-1 function, producing only one
answer,
thus not requiring backtracking to try other solutions.
member(X,[X|_]). |
(b)
This part requires you to extend the solution of part (a) to allow checking that a given power set is indeed the power set of a given set. We cannot use our earlier solution directly since it is an algorithm implementing a 1-1 function whcih produces a unique power set from a given set, with subsets and elements in a specific order. The input power set need not have the subsets and elements of subsets in the same order.
However, the input power set will be permutation of the
calculated power set of the input set (with the caveat that
corresponding power set elements, being sets themselves, can be
permutations of each other). Hence, we add another subsets
clause which applies when both the input set and power set are ground (nonvar
).
The input set Set
is used to calculate a power set SomePowerset
(using the first subsets
clause), which is then checked
against the input power set Powerset
using the permutation
predicate. We extend the permutation predicate from the class notes, by
allowing the list elements to be lists themselves (since the power set
contains sets). We add a check that the element S used to split the
list L is a permutation of the head H of the second list, and another
clause that allows atomic values to be trivially permutations of each
other.
Note that we could have also used the set equality predicate from Q.2(b) to compare the input power set against the calculated power set.
permutation(X,X) :- |
(c)
This part is the most free form: neither the set nor its power set is specified, we are expected to generate all possible bindings of < set, power set > combinations, using variables as placeholders, thus not distinguishing sets of the same size.
As it turns out, our earlier code can work just fine for this case
if we remove the check for Set
being ground and if we
also remove the final cut from the first subsets
clause,
allowing Prolog to backtrack after generating a solution. The
generation is seeded by the base clause of a null set, and then our
first main subsets
clause automatically starts generating
larger and larger sets.
% -- base case |
(d)
This part is also a search, we now want to invert the algorithm and find the set that can generate a given power set. We do this by extending our code for part (b). Our earlier code checks that a given set and power set are indeed in the right relation. We insert code that generates candidate sets and then continues with the earlier check.
Evidently, we need to limit the size of the candidate sets we
choose. We make an observation that eliminates the need for a search at
all. If a set S has power set PS, then S is itself an element of PS and
it is the longest set in PS. Hence, we add a longest(PowerSet,LongestSet,MaxLength)
predicate that is true if LongestSet
is the longest set
in PowerSet
and has length MaxLength
. Once
we compute the longest element of the input power set, we then use the
code from part (b) that checks whether a set's power set matches the
given power set.
There is an additional wrinkle though. We would keep this additional
code in a separate
clause, in which case it would be a copy of the clause in part (b) with
the additional code above. However, we would like to merge the 2
clauses together, so that we have one clause when Powerset
is free (handling parts (a) and (c)) and another clause when Powerset
is ground (handling parts (b) and (d)). Our additions above would break
the logic for part (b). Since the input set is specified, it will be
equal to the longest set in the input power set but may not have the
elements in the same order, i.e., the input set might be a permutation
of the longest set in the power set. So we add a call to permutations
to allow the input set to be a permutation of the longest set in the
power set.
Here's the code that handles all combinations of input and output parameter bindings. Note that we have retained the two cuts in the second clause, since backtracking is only needed in the first clause, to handle part (c). All other parts have unique solutions.
longest([S],S,N) :- |
Points awarded for each successful test input as listed below.
Partial credit awarded on case-by-case basis.
Input | Expected Output | Points |
---|---|---|
median([1],M). |
M = 1 ; |
5 |
median([7,2,4,5,3,6,1],M). |
M = 4 ; |
5 |
median([8,3,2,11,13,1,7],7). |
Yes |
5 |
median([1,2,3,4,5,6,7,8,9],4). |
No |
5 |
Q2.
(a)
setEq(S1,S2)
: 10 points
Input | Expected Output | Points |
---|---|---|
setEq([],[]). |
Yes |
2 |
setEq([george,richard,dubya],[richard,george,dubya]). |
Yes |
4 |
setEq([3,colin,3,paul],[paul,colin,3]). |
No |
4 |
Input | Expected Output | Points |
---|---|---|
setEq([a,[b,c],d],[d,[c,b],a]). |
Yes |
3 |
setEq([a,[b,c],[d]],[[a,b],[c,d]]). |
No |
3 |
setEq([a,[b,c],[c,b]],[a,[b,c],a]). |
No |
3 |
setEq([a,[b,[c,[d,[e,[f]]]]]],[[[[[[f],e],d],c],b],a]).
|
Yes |
3 |
setEq([a,[b,[c,[6,[e,[f]]]]]],[[[[[[f],e],d],c],b],a]).
|
No |
3 |
Q3.
soln([D,E,M,N,O,R,S,Y]
: 15 points
Input | Expected Output | Points |
---|---|---|
soln([D,E,M,N,O,R,S,Y]). |
D = 7 |
15 |
Q4.
(a)
subsets(S,PS)
: 10 points
Input | Expected Output | Points |
---|---|---|
subsets([],PS). |
PS = [[]] ; |
2 |
subsets([1,a,z],PS). |
PS = a set equal to [[], [1], [a], [z], [1,a], [1,z],
[a,z], [1,a,z]] ; |
4 |
subsets([scheme,sml,prolog,python,csharp],PS). |
PS = [.... all 32 subsets of
[scheme,sml,prolog,python,csharp] ...] ; |
4 |
Input | Expected Output | Points |
---|---|---|
subsets([],[[]]). |
Yes |
2 |
subsets([1,a,z],[[z], [a], [1], [], [1,a], [1,z],
[a,z], [1,a,z]]). |
Yes |
4 |
subsets([1,a,z],[[z], [a], [1], [1,a], [1,z], [a,z],
[1,a,z]]). |
No |
4 |
Input | Expected Output | Points |
---|---|---|
subsets(S,PS). |
(variable names not important below) S = [] |
2 4 4 |
Input | Expected Output | Points |
---|---|---|
subsets(S,[[]]). |
S = [] ; |
2 |
subsets(S,[[z], [a], [1], [1,a], [1,z], [a,z], [],
[1,a,z]]). |
S = a set equal to [1,a,z] ; |
4 |
subsets(S,[[z], [a], [1], [1,a], [1,z], [a,z],
[1,a,z]]). |
No |
4 |