This answer key is still under development.
In all of the problems below, your testing should show not just
that the predicates hold when given appropriate parameters, but also
whether they can be used to generate appropriate values. If they cannot
be used to generate values (e.g., most reverse predicates
can only reverse one of their two parameters), make sure to note these
limitations.
Note that I was somewhat generous in the design of this assignment as many of the answers can be found in Louden. I expected that those of you who took answers from Louden would at least attempt to explain them (and certainly test them better than he did). Of course, academic honesty requires that you cite such usage.
Even though I said that you should see whether your predicates could
generate values and note their limitations. Few of you explicitly
described restrictions. Some did some testing, but often that testing
was not very thorough. For example, you didn't see what happened if
you asked for more answers. Sometimes Prolog says no,
sometimes it gives a bad answer, sometimes it runs forever. In addition,
some of you forgot to do negative testing (that is, should the
predicate say "no" when it's supposed to say no).
A few of you seemed confused about the roles of lists as parameters. For example, I saw at least two assignments that said "if you insert the empty list into the empty list, you should get the empty list". It is more likely the case that "if you insert the empty list into the empty list, you'll get a list of the empty list".
Note that I took some liberties with the output from bp
to conserve space. In particular, I eliminated blank lines and tended
to join generated variables on the same line.
0. Helper Functions
I found it useful to have a few helper functions.
0.A. Concatenation.
First, it is nice to be able to concatenate (join) lists. I'll be using concatenation in my definition of insertion and perhaps in my definition of permutation.
% The concatenation of the empty list and any list is that list. concat([],L,L). % The concatenation of the list with first element X and remainder % Xs with a list, L, is a list with first element X and whose remainder % is the concatenation of L and Xs . concat([X|Xs], L, [X|Rest]) :- concat(Xs,L,Rest).
And some reasonable testing. First some tests to see whether it behaves and fails correctly on some "reasonable" lists.
?- concat([],[],[]). yes ?- concat([],[a],[a]). yes ?- concat([a],[b],[a,b]). yes ?- concat([a],[b],[a]). no ?- concat([a],[b],[b,a]). no ?- concat([a],[b],[a,b,c]). no
Now some testing to see if we can generate the concatenated list.
Note that in every case I asked for more answers to see if there was
any danger of incorrect alternate answers (or of running forever if
we backtracked beyond a call to concat).
?- concat([],[a,b,c],L). L=[a,b,c]; no ?- concat([a,b,c],[d,e,f],L). L=[a,b,c,d,e,f]; no
Now for some interesting testing in the reverse direction. Given a list, can we determine what lists might have been concatenated to create it? Phrased another way, "Given a list C, what lists L and M can be concatenated to form C?"
?- concat(L,M,[]). L=[], M=[]; no ?- concat(L,M,[a]). L=[], M=[a]; L=[a], M=[]; no ?- concat(L,M,[a,b,c]). L=[], M=[a,b,c]; L=[a], M=[b,c]; L=[a,b], M=[c]; L=[a,b,c], M=[]; no
How extreme can we go? How about, "For what lists, A, B, and C, is C the concatenation of A and B?"
Note that there was no restriction on B being a list since the design of our predicate doesn't require it.?- concat(A,B,C). A=[], B=_2295, C=_2295; A=[_2575], B=_2295, C=[_2575|_2295]; A=[_2575,_2581], B=_2295, C=[_2575,_2581|_2295]; A=[_2575,_2581,_2587], B=_2295, C=[_2575,_2581,_2587|_2295] yes
0.B. Equality
In addition, it can be useful to have an equal
predicate. Sometimes this is used to test equality. More frequently,
it is used to make a copy of one of its arguments (or portion thereof).
equal(X,X).
Which we can demonstrate with some simple tests.
Note that the penultimate one terminated rather than generating a sequence of values.?- equal(a,a). yes ?- equal([a,b,c],[a,b,c]). yes ?- equal(X,[a,b,c]). X=[a,b,c]; no ?- equal([a,b,c],X). X=[a,b,c]; no ?- equal([H|T],[a,b,c]). H=a, T=[b,c]; no ?- equal(X,Y). X=_2206, Y=_2206; no ?- equal(X, [X|Xs]). Boom!
Interestingly, we can even use equals with lists to "assign values to variables". For example,
?- equal([A,B,C],[1,2,3]). A=1, B=2, C=3; no ?- equal([A,B,3],[1,2,C]). A=1, B=2, C=3; no
0.C. Inequality
In testing my sorting routine, I may want to determine whether two lists of numbers are different. At first, this requires me to test whether two numbers are different. Since Prolog doesn't provide "real" negation, I'll use
% Two numbers are inequal if the first is less than the second or % greater than the second. Note that this cannot generate unequal % numbers; it can only test. Note also that it only works on numbers. unequal(X,Y) :- X < Y. unequal(X,Y) :- X > Y.
Here are a few sample illustrations of its use.
?- unequal(3,3). no ?- unequal(2,3). yes ?- unequal(2,4). yes ?- unequal(2,X). warning: *** bad arg in arithmetic operation *** warning: *** bad arg in arithmetic operation *** no ?- unequal([1],[2]). warning: *** bad arg in arithmetic operation *** warning: *** bad arg in arithmetic operation *** no
(It might be better to use not, but since we didn't
cover Prolog's not, I will avoid it.)
We can now write a "not the same list" predicate
% Two lists of numbers are different if their first elements are % different, their tails are different, or one is empty and the % other is not. Note that this predicate only works on lists of % numbers (because it uses unequal, which only works on numbers). differentLists([X|Xs],[Y|Ys]) :- unequal(X,Y). differentLists([X|Xs],[Y|Ys]) :- differentLists(Xs,Ys). differentLists([],[Y|Ys]). differentLists([X|Xs],[]).
?- differentLists([],[]). no ?- differentLists([1,2,3],[1,2,3]). no ?- differentLists([1,2,3],[1,3,2]). yes ?- differentLists([1,2,3],[1,2,3,4]). yes ?- differentLists([1,2,3],[1,2]). yes
1. Reversing Lists
Define and test a binary predicate, reverse(L1,L2), that
holds between two lists if and only if they contain the same elements,
but in the opposite order.
One such predicate can be found on page 441 of Louden. This one is modified slightly from that version.
% The reverse of the empty list is the empty list. reverse([], []). % L is the reverse of the list with first element X and remainder % Xs if L is constructed from the reversal of Xs and X (with X at % the end). reverse([X|Xs],L) :- reverse(Xs,L1), concat(L1,[X],L).
Does it work? A few simple tests suggest that it does.
?- reverse([],[]). yes ?- reverse([1,2,3],[3,2,1]). yes
Can it reverse lists? Yes, if we work in the forward direction.
?- reverse([1,2,3],X). X=[3,2,1] yes ?- reverse([],X). X=[] yes
It will also reverse lists in the backward direction.
?- reverse(X,[1,2,3]). X=[3,2,1].
However, the two differ in how they react to a "tell me more". In the
forward direction, it terminates with no (there are no other reversals).
In the backward direction, it runs forever. Why? One reason has to
do with the empty case. Certainly, reverse(R,[]) yields
R=[]. But what if we ask again? Then, there's no reason
not to match the second rule, setting R to [X|Xs].
This means that we need to prove reverse(XS,L1) and
concat(L1,[X],[]). The reverse can be proven by setting XS
and L1 to nil. But then we can't prove the concatenation. So we try
a length-one list, But that still won't concatenate with [X] and give
nil. So we try a length two list. And so on and so forth.
Can the reverse deal with lists that have some variables? Yes.
?- reverse([1,2,X],[3,2,Y]). X=3, Y=1 yes
What about very partial lists?
?- reverse([1|X],[2|Y]). X=[2], Y=[1]; X=[_2631,2], Y=[_2631,1]; X=[_2631,_2642,2], Y=[_2642,_2631,1] yes
Of course, one disadvantage of this function is that it takes O(n^2) time (since each concatenate is O(n) and there are n of them). We might try a related version that is tail recursive and that builds the reverse as we go.
% rev(X,Y,Z) - Z is the concatenation of (the reverse of X) and Y. rev([],L,L). rev([X|Xs],Helper,Result) :- rev(Xs,[X|Helper],Result). % R is the reverse of L if it's formed by concatenating the empty list % with the reverse of L. rev(L,R) :- rev(L,[],R).
This seems to have similar behavior to the previous version.
2. Insertion
Define and test a ternary predicate, insert(Old,Elt,New),
that holds if and only if New is the same as Old
except that one copy of Elt has been added at some position
(not necessarily the front). For example,
insert([a,c], b, [b,a,c])
and
andinsert([a,c], b, [a,b,c])
all hold.insert([a,c], b,[a,c,b])
The difficulty here was ensuring that insertion works at each place in the list. As in many cases, this can be done recursively.
% The list with first element X and remainder L is a list formed by % inserting X in L. insert(L,X,[X|L]). % The list M is a list formed by inserting X in L if the first elements % of the lists are the same and the remainder of M is the result of % inserting X in the remainder of L. insert([Y|Ys], X, [Y|Remainder]) :- insert(Ys,X,Remainder).
And some very simple tests.
Does it work for various positions in a list?
?- insert([1,2,3],4,[4,1,2,3]). yes ?- insert([1,2,3],4,[1,4,2,3]). yes ?- insert([1,2,3],4,[1,2,4,3]). yes ?- insert([1,2,3],4,[1,2,3,4]). yes
Can we generate the result list?
?- insert([1,2,3],4,L). L=[4,1,2,3]; L=[1,4,2,3]; L=[1,2,4,3]; L=[1,2,3,4]; no
Given the result list and the inserted element, can we geneate the original list?
?- insert(L, 1, [1,2,3]). L=[2,3]; no ?- insert(L, 2, [1,2,3]). L=[1,3]; no ?- insert(L, 3, [1,2,3]). L=[1,2]; no ?- insert(L, 4, [1,2,3]). no ?- insert(L,1,[1,1,1]). L=[1,1]; L=[1,1]; L=[1,1]; no ?- insert(L,1,[1,a,1,b,1]). L=[a,1,b,1]; L=[1,a,b,1]; L=[1,a,1,b]; no
Given the result list and original list, can we determine which element was inserted?
?- insert([1,2,3],X,[4,1,2,3]). X=4 yes ?- insert([1,2,3],X,[1,4,2,3]). X=4 yes ?- insert([1,2,3],X,[1,2,3,4]). X=4; no ?- insert([1,1,1],X,[1,1,1,1]). X=1; X=1; X=1; X=1;
Given the result list, can we determine the original list and the element inserted?
?- insert(Original,Elt,[1,2,3,4]). Original=[2,3,4], Elt=1; Original=[1,3,4], Elt=2; Original=[1,2,4], Elt=3; Original=[1,2,3], Elt=4; no
Here are some questions with negative answers.
?- insert([1,2,3],X,[1,2,3]). no ?- insert(L,2,[1,1]). no ?- insert([1,2,3],X,[2,3,1,4]). no
There were some subtleties in the definition. In particular, you need to make sure that the recursive case maintains the first element of the list. Here's one that doesn't.
odd_insert(Old, Elt, [Elt|Old]). odd_insert([X|Xs], Elt, [Y|Ys]) :- odd_insert(Xs, Elt, Ys).
This method incorrectly answers some questions as yes. For example,
?- odd_insert([2],1,[3,1]). yes
Of course, this behavior is easy to identify with standard testing. For example,
?- odd_insert([2],1,L). L=[1,2]; L=[_2551,1]; no
3. Removal
Define and test a ternary predicate, remove(Old,Elt,New),
that holds if and only if New is the same as Old
except that one copy of Elt has been removed.
Here's an easy version: New is the result of removing
Elt from Old if Old can be formed
by inserting Elt into New
remove(Old,Elt,New) :- insert(New,Elt,Old).
Our previous expreriments suggest that it will work. Here are a few samples without narrative. Note that it does correctly answer negative questions as well as positive.
?- remove([1,2,3,4],1,[2,3,4]). yes ?- remove([1,2,3,4],3,[1,2,4]). yes ?- remove([1,2,3,4],4,[1,2,3,4]). no ?- remove([1,2,3,4],4,[1,2,3]). yes ?- remove([1,2,3,4],4,[3,2,1]). no
Are there other ways to define remove? Certainly, we could
simply define it recursively, similarly to the way we defined
insert.
% If L has the form [X|Xs], then Xs is L with X removed. rem([X|Xs],X,Xs). % If L has the form [Y|Ys], then [Y|Zs] is L with X removed % Zs is Ys with X removed. Stated in another way, if L and M % have the first element, then M is L with X removed if the % remainder of M is the remainder of L with X removed. rem([Y|Ys],X,[Y|Zs]) :- rem(Ys,X,Zs).
A few of the more general tests.
?- rem([1,2,3,4],X,L). X=1, L=[2,3,4]; X=2, L=[1,3,4]; X=3, L=[1,2,4]; X=4, L=[1,2,3]; no ?- rem(L,X,L). Boom! ?- rem(L,4,[1,2,3]). L=[4,1,2,3]; L=[1,4,2,3]; L=[1,2,4,3]; L=[1,2,3,4]; no
You can also build remove with append. Louden
does something like that in his definition of permutation
on p. 450.
% The list New is formed by deleting Elt from Old if there % are list Pre and Post such that Old is formed by % Pre-Elt-Post (with appropriate treatment of list structures) % and new is formed by Pre-Post. loudrem(Old,Elt,New) :- append(Pre,[Elt|Post],Old), append(Pre,Post,New).
And one short test.
?- loudrem([1,2,3],Elt,L). Elt=1, L=[2,3]; Elt=2, L=[1,3]; Elt=3, L=[1,2]; no
4. Permutations
Define and test a binary predicate, permutation(L1,L2),
that holds between two lists if and only if they contain the same
elements (but possibly in another order).
The definition naturally corresponds to the Scheme definition from our previous assignment. However, Prolog's ability to backtrack makes this much easier than it was in Scheme.
% The empty list is a permutation of the empty list. perm([],[]). % P is a permutation of the list with first element X and remainder % Xs if P is formed by inserting X into a permutation of Xs. (How's % that for restating the Prolog code without gaining anything?) perm([X|Xs],P) :- perm(Xs,Supperm), insert(Subperm,X,P).
And a few simple tests (getting less documented as time continues).
?- perm([],[]). yes ?- perm([1],[1]). yes ?- perm([1],[2]). no ?- perm(X,[1]). X=[1]; Boom! ?- perm([1],X). X=[1]; no ?- perm([1,2,3],P). P=[1,2,3]; P=[2,1,3]; P=[2,3,1]; P=[1,3,2]; P=[3,1,2]; P=[3,2,1]; no ?- perm(P,[1,2,3]). P=[1,2,3]; P=[2,1,3]; P=[3,1,2]; P=[1,3,2]; P=[2,3,1]; P=[3,2,1]; Boom! ?- perm(L,L). L=[]; L=[_2439]; L=[_2439,_2447]; L=[_2439,_2439]; L=[_2439,_2447,_2455]; L=[_2439,_2439,_2455]; L=[_2439,_2439,_2439]; L=[_2439,_2447,_2447]; L=[_2439,_2439,_2439]; L=[_2439,_2447,_2439]; L=[_2439,_2447,_2455,_2463] yes ?- perm(L,M). L=[], M=[]; L=[_2455], M=[_2455]; L=[_2455,_2463], M=[_2455,_2463]; L=[_2455,_2463], M=[_2463,_2455]; L=[_2455,_2463,_2471], M=[_2455,_2463,_2471]; L=[_2455,_2463,_2471], M=[_2463,_2455,_2471] yes
Louden also defines a permutation predicate on p. 450. His is written as something like
% The empty list is a permutation of the empty list. permutation([],[]). % A list L is a permutation of the list with first element X and % remainder Xs if X appears somewhere in L and Xs is a permutation % of of L without X. permutation(L,[X|Xs]) :- append(BeforeX, [X|AfterX], L), append(BeforeX, AfterX, LwithoutX), permutation(LwithoutX, Xs).
His has the disadvantage of recursing infinitely when the first list is not specified and the first answer is rejected, as in
?- permutation(X,[2]). X=[2]; Boom! ?- permutation(X,[1,2,3]). Boom!
However, it does work correctly in the other direction.
?- permutation([1,2,3],X). X=[1,2,3]; X=[1,3,2]; X=[2,1,3]; X=[2,3,1]; X=[3,1,2]; X=[3,2,1]; no
One of you had an interesting variant in which you removed the head of the first list from the second and then permuted the rest of the first list. This appears to be a shorthand
perm1([],[]). perm1([X|Xs], L) :- remove(L,X,NewL), perm1(Xs,NewL).
What happens with this one?
?- perm1([1,2,3],[3,1,2]). yes ?- perm1([1,2,3],L). L=[1,2,3]; Boom! ?- perm1(L,[1,2,3]). L=[1,2,3]; L=[1,3,2]; L=[2,1,3]; L=[2,3,1]; L=[3,1,2]; L=[3,2,1]; no
A slight variation of the definition makes some difference.
perm2([],[]). perm2([X|Xs], L) :- perm2(Xs,NewL), remove(L,X,NewL).
?- perm2([1,2,3],L). L=[1,2,3]; L=[2,1,3]; L=[2,3,1]; L=[1,3,2]; L=[3,1,2]; L=[3,2,1]; no ?- perm2(L,[1,2,3]). L=[1,2,3]; L=[2,1,3]; L=[3,1,2]; L=[1,3,2]; L=[2,3,1]; L=[3,2,1]; Boom!
And one more variation
perm3([],[]). perm3([X|Xs], L) :- perm3(NewL,Xs), remove(L,X,NewL).
?- perm3([1,2,3],L). L=[1,2,3]; L=[2,1,3]; L=[2,3,1]; L=[1,3,2]; L=[3,1,2]; L=[3,2,1]; Boom! ?- perm3(L,[1,2,3]). L=[1,2,3]; L=[2,1,3]; L=[3,1,2]; L=[1,3,2]; L=[2,3,1]; L=[3,2,1]; Boom!
A few of you wrote redundant definition so that when you computed permutations, you computed some permutations multiple times. Good design suggests that you try not to write redundant definitions.
5. Sorting Lists
Define and test a binary predicate, sorted(L1,L2), that holds
between two lists if, and only if, the second contains the same elements
as the first, but in sorted order. You may use any strategy you deem
appropriate, but you should indicate what the approximate running time is
in big-O notation.
5.A. Definition
As Louden suggests on p. 450, an easy "logical" sorting algorithm is one based on permutations and checking whether a list is sorted. In particular, S is a sorted version of L if S is sorted and S is a permutation of L.
sorted(L,S) :- permutation(L,S), sorted(S).
How do we determine if a list is sorted? Fairly easily.
% The empty list is sorted sorted([]). % The singleton list is sorted sorted([X]). % A list is sorted if the first two elements are in % order and all but the first are sorted. sorted([First,Second|Rest]) :- First =< Second, sorted([Second|Rest]).
5.B. Running Time
What is the running time of this sorting method? Well, to generate a permutation of length N takes about O(N) steps (that's to generate one permutation) because there are a constant number of predicates to resolve. To test whether a list is sorted takes O(N) steps. At worst, we generate O(N!) permutations, so the overall running time is O(N*N!) which is O(N!).
5.C. Simple Tests
And some of our joyful tests.
?- sorted([]). yes ?- sorted([1]). yes ?- sorted([a]). yes ?- sorted([1,2,3]). yes ?- sorted([3,1,2]). no ?- sorted([2,1,3]). no ?- sorted([2,1,3],[1,2,3]). yes ?- sorted([2,1,3],L). L=[1,2,3]; no ?- sorted(L,[2,1,3]). Boom!
5.D. Comprehensive Tests
Note that there is less need to do comprehensive testing of this sorting procedure because it obviously meets many of the criteria of our testing method ("for all permutations": well, we're generating all permutations naturally).
Nonetheless, one might write something that generates all the permutations and tries to sort each one. As in an earlier assignment, we'll start with a sorted list and then check to make sure that the sorted version of a permutation is equal to the original.
trySortingPerms(SortedList) :- perm(SortedList,L), % Generate a permutation sorted(L,S), % Sort it write(0), % Write something equal(SortedList,S), % Make sure the sort is correct write(1), % Write something else fail. % Back up and try again.
If we ever see two 0's in a row, we know that the sorting method failed. For example,
Of course, this doesn't give us a great answer (that is, it doesn't report whether the sorting method worked) because we don't have an easy way to both try all permutations and succeed. However, a quick visual check shows that 0's and 1's alternate. Why are there so many more pairs in the last case? Because multiple permutations of?- trySortingPerms([1,2,3,4]). 010101010101010101010101010101010101010101010101 no ?- trySortingPerms([1,2,3,4,5]). 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 0101010101010101 no ?- trySortingPerms([1,2,2,3,3]). 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101010101010101010101010101010101010101010101010101 01010101 no
[1,2,2,3,4] are sorted (so when we backtrack, we try another
sort before trying another permutation to sort).
More importantly, I haven't tested a wide variety of lists. It is certainly possible to do so, but not worth the space here.
Since Prolog doesn't appear to provide higher-order operations, to test
different sorting we'll need to rewrite trySortingPerms. Here's
a a test of a non-sorting operation (using equal to sort).
,boxcode>
trySortingPerms2(SortedList) :-
perm(SortedList,L), % Generate a permutation
equal(L,S), % Sort it
write(0), % Write something
equal(SortedList,S), % Make sure the sort is correct
write(1), % Write something else
fail. % Back up and try again.
And a test.
Observe that the 0's and 1's did not alternate evenly as in the first case.?- trySortingPerms2([1,2,2,3,3]). 01000001000000000000000000000000000000000000000000000000 00000001000001000000000000000000000000000000000000000000 000000000000 no
5.E. Alternate Tests
We might also compare the sorted list to the original list and, if they're different, report an error.
sortFailsToSort(SortedList) :- perm(SortedList,L), % Generate a permutation. sorted(L,S), % Sort it. differentLists(SortedList,S), % See if the result is not sorted write('Failed to correctly sort '), write(L), % Print the erroneously sorted list. nl. equalFailsToSort(SortedList) :- perm(SortedList,L), % Generate a permutation. equal(L,S), % Sort it ("fake" sorting routine) differentLists(SortedList,S), % See if the result is not sorted write('Failed to correctly sort '), write(L), % Print the erroneously sorted list. nl.
And some testing with those procedures.
?- sortFailsToSort([1,2,3]). no ?- sortFailsToSort([1,2,2,3,3]). no ?- equalFailsToSort([1,2,3]). Failed to correctly sort [2,1,3] yes ?- equalFailsToSort([1,1,1]). no
We can even use not to make our testing a little bit clearer.
6. Inserting Elements in Search Trees
Write and test a ternary predicate, insert(Old,Elt,New),
that holds if and only if New results from inserting
Elt in the search tree Old. Recall that
a search tree is a binary tree in which the left subtree of a node
contains values smaller than the node and the right subtree contains
values bigger than the node. You can use node(Val,Left,Right)
and empty as your tree constructors.
We wrote something fairly close in class.
% The single node search tree with Elt at the root is the search % tree that results from inserting Elt into the empty search tree. st_insert(leaf,Elt,node(Elt,leaf,leaf)) % Inserting an element matching the root into a search tree doesn't % affect the search tree. st_insert(node(Root,Left,Right), Root, node(Root,Left,Right)). % (English matches Prolog for the next few) st_insert(node(Root,Left,Right), Elt, node(Root,NewLeft,Right)) :- Elt < Root, st_insert(Left, Elt, NewLeft). st_insert(node(Root,Left,Right), Elt, node(Root,Left,NewRight)) :- Elt > Root, st_insert(Right, Elt, NewRight).
And a few short tests done as one big command.
?- st_insert(leaf,2,T1), st_insert(T1,1,T2), st_insert(T2,3,T3), st_insert(T3,4,T4), st_insert(T4,2,T5), st_insert(T5,4,T6), st_insert(T6,3.5,T7). T1=node(2,leaf,leaf), T2=node(2,node(1,leaf,leaf),leaf), T3=node(2,node(1,leaf,leaf),node(3,leaf,leaf)), T4=node(2,node(1,leaf,leaf),node(3,leaf,node(4,leaf,leaf))), T5=node(2,node(1,leaf,leaf),node(3,leaf,node(4,leaf,leaf))), T6=node(2,node(1,leaf,leaf),node(3,leaf,node(4,leaf,leaf))), T7=node(2,node(1,leaf,leaf), node(3,leaf, node(4,node(3.500000,leaf,leaf), leaf)))
Sorry, no negative tests this time.
7. Building Search Trees
Write and test a binary predicate, build(Tree,List), that
holds if and only if Tree is a search tree that can be
built from List by inserting the elements of List
into the empty tree in order. (Hmmm ... sounds more like a procedure
than a predicate, doesn't it?)
I found it easiest to write a recursive version using a helper function.
% build(Old,List,New): New is the result of inserting all of % the elements of List into Old. % When we insert no new elements in a tree, we get that tree. build(Tree,[],Tree). % When we insert the list with first element X and remainder Xs % into a Tree, we get the same tree as we'd get by inserting X % and then inserting Xs. build(Tree,[X|Xs],NewTree) :- st_insert(Tree,X,Subtree), build(Subtree,Xs,NewTree). % To build tree T from list L, we build the tree we'd get from % L starting with the empty tree build(T,L) :- build(leaf,L,T).
Could we then do interesting things? Certainly. Note that I asked for "more answers" in each case to see what happened. In each case but the last, it terminated successfully.
?- build(T,[]). T=leaf; no ?- build(T,[2]). T=[2|leaf]; no ?- build(T,[2,1,3]). T=node(2,node(1,leaf,leaf),node(3,leaf,leaf)); no ?- build(T,[2,1,3,1.5,4,3.5,2,4]). T=node(2,node(1,leaf,node(1.500000,leaf,leaf)), node(3,leaf,node(4,node(3.500000,leaf,leaf),leaf))); no ?- build(node(2,node(1,leaf,leaf),node(3,leaf,leaf)), L). Serious boom! (Prolog exited) ?- build(T,[2,1,3]), build(T,L). Serious boom! (Prolog exited)
Note that the last one indicates a deficiency of Prolog. Any good mathematician should be able to look at that last question and say "T=[2,1,3]".
Some of you approached this problem in a different way. Instead of using a helper function, you recursed on the remainder of the list, as in
% The empty tree is built from the empty list. build_tree(leaf, []). % Tree T is built from the list with first element X and % remainder Xs if T is built by inserting X into the tree % built from Xs. build_tree(T,[X|Xs]) :- build_tree(Subtree,Xs), st_insert(Subtree,X,T).
Let's try it.
?- build_tree(T,[2,1,3]). T=node(3,node(1,leaf,node(2,leaf,leaf)),leaf); no
Note that this built the tree in a backwards order (as you might expect given the definition). Although the problem said "inserting the elements in order" I accepted answers like this. A better definition might reverse the list first.
What happens with my "killer" example? We'll first do a sensible version and then the deadly one.
?- build_tree(node(3,node(1,leaf,node(2,leaf,leaf)),leaf), [2,1,3]). yes ?- build_tree(T,[2,1,3]), build_tree(T,L). warning: *** bad arg in arithmetic operation *** warning: *** bad arg in arithmetic operation *** warning: *** bad arg in arithmetic operation *** ... (ad infinitum)
8. Relating Predicates
What is the relationship between the insert and
remove predicates you wrote in parts 2 and 3? Should
they be the same? Are they?
In my particular implementation, they are most certainly the same, since
I just made remove call insert. The more
explicit definition of rem also had close resemblance to
insert, although "turned around" as it were. The Loudenesque
version of remove was less natural to write as an
insert, but we could certainly do so.
Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
Source text last modified Fri May 8 10:52:13 1998.
This page generated on Fri May 8 10:57:03 1998 by SiteWeaver.
Contact our webmaster at rebelsky@math.grin.edu