TEACH SETS2.ANS Aaron Sloman Feb 1984
Tidied up, and brought up to date, 7 Dec 1996
Changes in Dec 1996 include:
(a) removing spurious lvars declarations for input and output
locals
(b) using "!" in the examples based on the matcher, but
reducing the use of the matcher
(c) some comments on the functional style
(d) a few other changes
(e) revised solutions to the subculture problem.
Sample answers to TEACH * SETS2
===============================
CONTENTS - (Use <ENTER> g to access required sections)
-- A COLLECTION OF PREDICATES
-- . Using partial application to define positive and negative
-- MUCHLESS
-- ISSUBSET
-- REDUNDANT
-- EXISTS
-- FINDONE
-- ALL
-- HASODD HASEVEN ALLODD ALLEVEN
-- FINDALL
-- PRUNE
-- UNION
-- INTERSECTION
-- LARGER
-- HOWMANY
-- MORE
-- MOST
-- REMOVEALL
-- SUBTRACT
-- OVERLAPS EXCLUDES
-- SUBCULTURE
-- . subculture version 1
-- . Test cases for subculture
-- . subculture version 2
-- . subculture version 4
-- . subculture version 5
-- QUESTION 24
-- A COLLECTION OF PREDICATES -----------------------------------------
The procedure positive takes a number and returns true if the number
is positive, i.e. greater than 0. This can be defined in a number
of different styles, some more verbose than others, and with or without
an output variable. Using an output variable is more verbose but makes
it clearer what the procedure does, when someone reads the program.
define positive(item) -> boolean;
;;; verbose version, with header clearly showing what the procedure
;;; is about
if item > 0 then
true -> boolean
else
false -> boolean
endif;
enddefine;
define positive(item) -> boolean;
;;; Slightly more compact version. Evaluate conditional, then
;;; assign the result to the output local
if item > 0 then
true
else
false
endif -> boolean;
enddefine;
define positive(item) -> boolean;
;;; Even more compact version - assign the result of ">" to
;;; the output local
item > 0 -> boolean
enddefine;
define positive(item);
;;; Functional style of programming.
;;; Just leave the result of ">" as the result of this procedure.
item > 0
enddefine;
;;; Each of the following could also be done in all the above styles.
define negative(item) -> boolean;
item < 0 -> boolean
enddefine;
define even(item) -> boolean;
(item mod 2) == 0 -> boolean;
enddefine;
Note: for comparing words or integers or constant items like [], it is
slightly to use "==" than "=".
This has nothing to do with the use of "=" and "==" in lists patterns.
See HELP * EQUAL for more on this.
NEVER use "==" to compare decimal numbers. (Floats).
define odd(item) -> boolean;
(item mod 2) /== 0 -> boolean
enddefine;
-- . Using partial application to define positive and negative
Skip to next subheading if you don't know about 'partial application'.
If you have learnt about 'partial application' (see HELP * CLOSURES or
the second half of TEACH PERCENT, or HELP PARTAPPLY),
then you could have defined positive and negative by partially applying
the operators > and < to 0.
There are two points to note. One is that partial application uses
(% .. %) for the arguments to the procedure to say, do not run this
procedure now but create a new version which can be run later and will
already have these arguments. The other point is that because ">" and
"<" are names of infix operators, you have to use "nonop" in front of
them to prevent pop-11 immediately trying to run the corresponding
procedures.
vars positive;
nonop >(%0%) -> positive; ;;; partially apply ">" to 0
vars, negative;
nonop <(%0%) -> negative; ;;; partially apply "<" to 0
The nonop part is there to stop the procedure > (or <) actually attempting
to run, which it would normally do since it is an 'infix' operator.
Those declarations and assignments can be combined, thus:
vars
positive = nonop >(%0%),
negative = nonop <(%0%);
Alternatively you can use this format
define positive = nonop >(%0%)
enddefine;
define negative = nonop <(%0%)
enddefine;
/*
;;; tests
positive(6) =>
positive(-6) =>
positive(0) =>
positive(4.5) =>
negative(6) =>
negative(-6) =>
negative(0) =>
negative(4.5) =>
*/
Note that according to those definitions 0 is neither positive nor
negative.
-- MUCHLESS -----------------------------------------------------------
define muchless(num1, num2) -> boole;
num2 - num1 > 100 -> boole;
enddefine;
-- ISSUBSET -----------------------------------------------------------
define issubset(list1, list2) -> boolean;
lvars item;
for item in list1 do
unless member(item, list2) then
;;; Item from list1 not in list2, so
false -> boolean; return()
endunless
endfor;
;;; Everything in list1 was found to be in list2, so
true -> boolean;
enddefine;
;;; Here is a version using "returnunless" in this format
;;; returnunless( <condition> )( <action> )
;;;; Meaning unless <condition> then <action>; return() endunless;
define issubset(list1, list2) -> boolean;
lvars item;
for item in list1 do
returnunless( member(item, list2) )(false -> boolean)
endfor;
true -> boolean;
enddefine;
The next version uses recursion, in the functional style. People who
like this style do not use output local variables. The result of the
procedure is simply whatever is left on the stack, in this case true
or false.
define issubset(list1, list2);
;;; The empty list is a subset of every list.
if list1 == [] then true
;;; otherwise if list2 is empty, then list1 cannot be a subset
elseif list2 == [] then false
;;; see if tail of list1 is a subset of list2. If so, see if the
;;; head is a member. It could be done the other way round.
elseif issubset(tl(list1), list2) and member(hd(list1), list2) then
true
else
;;; all conditions for being a subset failed, so
false
endif
enddefine;
Here is the same thing with an output local variable, so that someone
looking at the header can see what sort of result the procedure produces
define issubset(list1, list2) -> boolean;
if list1 == [] then true
elseif list2 == [] then false
elseif issubset(tl(list1), list2) and member(hd(list1), list2) then
true
else
false
endif -> boolean
enddefine;
Write some test cases and check that the procedure works.
-- REDUNDANT ----------------------------------------------------------
;;; This procedure takes a list of items and returns a boolean result
define redundant(list) -> boolean;
;;; return true if the list is redundant, otherwise false
lvars list, item; ;;; pattern variables
while list matches ! [?item ??list] do
if member(item, list) then true -> boolean; return(); endif;
endwhile;
false -> boolean;
enddefine;
The line containing member could be changed to
returnif( member(item, list) )(true -> boolean)
Using the matcher is not the most efficient way. Here is an answer that
does not use the matcher and is better programming style because it
is more efficient, and probably clearer.
define redundant(list) -> boolean;
lvars sub;
;;; Use "for ... on ...do" to get successive tails.
;;; See HELP * FOR
for sub on list do
if member(hd(sub), tl(sub)) then
true -> boolean;
return()
endif
endfor;
false -> boolean;
enddefine;
;;; Here is a recursive, functional style version,without output local
define redundant(list);
;;; an empty list is not redundant
if list == [] then
false
;;; if the first element is in the tail then it is
elseif member(hd(list), tl(list)) then
true
;;; or if the tail is redundant then it is
elseif redundant(tl(list)) then
true
else
;;; all forms of redundancy ruled out
false
endif
enddefine;
-- EXISTS -------------------------------------------------------------
This procedure, defined in TEACH SETS2 takes a list and a predicate, and
returns true if at least one thing in the list satisfies the predicate,
otherwise it returns false.
It could also have been defined thus
define exists(list, pred) -> found;
;;; because pred is a local lvar, we have to insert the
;;; procedure itself, not the word "pred" as the restriction
if list matches ! [== ?found: ^pred ==] then
true -> found
else
false -> found
endif
enddefine;
The next version avoids the need for "^" before pred, but is
not a good way to program, as the variable pred is not lexically
scoped, and could interact with something else.
define exists(list, pred) -> found;
vars pred; ;;; make pred dynamically scoped
if list matches ! [== ?found: pred ==] then
true -> found
else
false -> found
endif
enddefine;
The next version uses a loop instead of the matcher, which is more
efficient. We also declare the input variable "pred" to be of
type procedure. That means that
(a) if you give exists a second argument that is not a procedure you
will get an error message, which is useful for finding bugs in your
programs, and
(b) exists will test when it starts that pred is a procedure, so that
the calls in the loop do not all need to involve the test. This makes
the program somewhat more efficient. If a variable is not declared to be
of type procedure, then whenever it is used in a procedure call pop-11
does a check at run time that it has a procedure value.
(See HELP EFFICIENCY if you want to know more.)
define exists(list, procedure pred) -> found;
lvars item;
for item in list do
if pred(item) then true -> found; return() endif;
endfor;
false -> found;
enddefine;
;;; The next version uses "returnif" in this format
;;; returnif( <condition> )( <action> )
;;; Meaning if <condition> then <action>; return(); endif;
define exists(list, procedure pred) -> found;
lvars item;
for item in list do
returnif(pred(item))(true -> found)
endfor;
false -> found;
enddefine;
Here is a version that returns the item found, or false. Usually
returning the item found is more useful than returning true.
See findone below.
define exists(list, procedure pred) -> found;
lvars item;
for item in list do
returnif( pred(item) )( item -> found )
endfor;
false -> found;
enddefine;
This one uses "item" as the output local, saving a local variable.
In that case the return instruction can be simplified.
define exists(list, procedure pred) -> item;
for item in list do
returnif( pred(item) )
endfor;
false -> item;
enddefine;
Here is a recursive version, in the functional style
define exists(list, procedure pred);
if list == [] then
false
elseif pred(hd(list)) then
true
else
exists(tl(list), pred)
endif
enddefine;
-- FINDONE ------------------------------------------------------------
This is essentially the same as exists, except for the result returned.
define findone(list, procedure pred) -> item;
for item in list do
;;; the result returned will be item if pred(item) is true
returnif( pred(item) )
endfor;
false -> item;
enddefine;
NOTE
returnif( < expression > )
is equivalent to
if < expression > then return() endif
See REF * SYNTAX/returnif REF * SYNTAX/returnunless
Here is a recursive version in the functional style
define findone(list, procedure pred);
if list == [] then false
elseif pred(hd(list)) then hd(list)
else findone(tl(list), pred)
endif
enddefine;
-- ALL ----------------------------------------------------------------
define all(list, procedure pred) -> boolean;
;;; return true if all items in the list satisfy pred, otherwise
;;; false
lvars item;
for item in list do
unless pred(item) then false -> boolean; return() endunless;
endfor;
true -> boolean;
enddefine;
Alternatively:
define all(list, pred) -> boolean;
lvars item;
for item in list do
returnunless ( pred(item) )( false -> boolean)
endfor;
true -> boolean;
enddefine;
Another way to define this would use <> to join the procedure
pred to the procedure not, producing a new procedure which always
produces the opposite truth value to that produced by pred.
define all(list, pred) -> boolean;
not( exists( list, pred <> not) )
enddefine;
That has the disadvantage that every time you run it it has to create
the procedure pred <> not, which will be used temporarily then
discarded, increasing the time spent doing garbage collection.
But it is a nice elegant solution.
-- HASODD HASEVEN ALLODD ALLEVEN --------------------------------------
This is defined here in the functional style. It might be better
to use output locals, e.g. "-> boolean"
define hasodd(list);
exists(list, odd)
enddefine;
HASEVEN can be defined similarly.
Or, using partial application:
vars procedure(hasodd, haseven);
exists(%odd%) -> hasodd;
exists(%even%) -> haseven;
or, equivalently:
vars procedure(
hasodd = exists(%odd%),
haseven = exists(%even%)
);
or
define hasodd = exists(%odd%)
enddefine;
define haseven = exists(%even%)
enddefine;
hasodd([ 3 5 7 2 4 5 6 8]) =>
haseven([ 3 5 7 2 4 5 6 8]) =>
hasodd([ 2 4 6 8]) =>
haseven([ 3 5 7 5 ]) =>
Compare the result if exists is defined so as to return the object
found, instead of true, or if you use findone instead of exists.
Similarly, allodd and alleven can be defined using partial application;
vars procedure(
allodd = all(%odd%),
alleven = all(%even%)
);
or
define allodd = all(%odd%)
enddefine;
define alleven = all(%even%)
enddefine;
-- FINDALL ------------------------------------------------------------
A recursive version
define findall(list, procedure pred) -> newlist;
;;; return a list of all items in list that satisfy pred
if list == [] then
[] -> newlist
elseif pred(hd(list)) then
[^(hd(list)) ^^( findall(tl(list), pred) )] -> newlist
else
findall( tl(list), pred ) -> newlist
endif
enddefine;
An iterative version using the matcher. Note that each time round the
loop the variable list has as its value a smaller sublist.
define findall(list, procedure pred) -> newlist;
lvars item;
[% while list matches ! [ == ?item: ^pred ??list] do item endwhile %]
-> newlist;
enddefine;
A more efficient iterative version, possibly clearer too:
define findall(list, procedure pred) -> newlist;
lvars item;
[% for item in list do if pred(item) then item endif endfor%]
-> newlist;
enddefine;
A recursive version in the functional style
define findall(list, pred) -> newlist;
if list == [] then
[]
elseif pred(hd(list)) then
hd(list) :: findall(tl(list), pred)
else
findall(tl(list), pred)
endif
-> newlist;
enddefine;
/*
;;; some tests
findall([], isword) =>
** []
findall([a 1 b 2 c 3], isinteger) =>
** [1 2 3]
findall([a 1 b 2 c 3], isword) =>
** [a b c]
*/
-- PRUNE --------------------------------------------------------------
define prune(list) -> result;
;;; Make a copy of the list which does not contain repeated items.
lvars sub, item;
[%
;;; use "on" so sub has successive tails as values
for sub on list do
hd(sub) -> item;
unless member(item, tl(sub)) then item endunless
endfor
%] -> result;
enddefine;
A still more efficient version using the procedure dest, which, when
applied to a list returns both its head and its tail. See * dest
define prune(list) -> result;
lvars item;
[%
until list == [] do
dest(list) -> (item, list);
unless member(item, list) then item endunless
enduntil
%] -> result;
enddefine;
Here is a recursive version in the functional style
define prune(list) -> result;
if list == [] then
[]
elseif member(hd(list), tl(list)) then
prune(tl(list))
else
hd(list) :: prune(tl(list))
endif -> result
enddefine;
/*
;;; test examples
prune([A B 1 2 C 2 1 B A])=>
** [C 2 1 B A]
rev(prune(rev([A B 1 2 C 2 1 B A]))) =>
** [A B 1 2 C]
*/
The above definitions of prune do not return items in the order
specified in the question, in which the first occurrence is kept
and subsequent ones removed.
For that you have to reverse the list before giving it to prune,
and then reverse the result. Why?
Here is a recursive version that produces the result specified in the
question, i.e. it does it in the right order. But it requires deleting
items from the tail of the list before running the recursive call.
define prune(list) -> result;
if list == [] then
[]
elseif member(hd(list), tl(list)) then
hd(list) :: prune( delete(hd(list), tl(list)) )
else
hd(list) :: prune( tl(list) )
endif -> result;
enddefine;
/*
;;; try these tests, tracing prune
prune([A B 1 2 C 2 1 B A])=>
rev(prune(rev([A B 1 2 C 2 1 B A]))) =>
*/
A disadvantage of this version is that the use of delete before the
recursive call of prune means that the rest of the list is traversed
TWICE, once to delete items equal to the head of the list, and then once
to prune the tail. And that is AFTER member has already traversed the
tail.
So if you care about efficiency it is better not to worry about creating
the pruned list in the right order. However, sometimes you need to
preserve the order of a list, e.g. when all items are stored in
alphabetical order in lists.
-- UNION --------------------------------------------------------------
This version is wasteful. Why?
define union(list1, list2) -> result;
;;; return a list containing all elements of list1 and list2 without
;;; any repeats
prune( [^^list1 ^^list2] ) -> result;
enddefine;
Slightly more efficient perhaps:
define union (list1, list2) -> newlist;
lvars item;
[
%
for item in list1 do
unless member(item, list2) then item endunless
endfor
%
^^list2
] -> newlist;
;;; optional extra
;;; sort(newlist) -> newlist;
enddefine;
If the union is to be alphabetically ordered, or numerically ordered,
end with
sort(newlist) -> newlist
in the procedure. This will work only if the original list contains only
words and strings, or only numbers.
-- INTERSECTION -------------------------------------------------------
define intersection(list1, list2) -> newlist;
;;; return a list containing all items that occur in both lists.
lvars item;
[% for item in list1 do
if member(item, list2) then item endif
endfor %] -> newlist;
enddefine;
Note that in this version of intersection, items are put in newlist
in the order in which they were in list1. This may make sorting
unnecessary.
-- LARGER -------------------------------------------------------------
define setsize(list);
;;; If list may contain repreated elements use "setsize" to
;;; find the number of distinct items in it.
;;; This is wasteful because it first creates the pruned list.
length(prune(list))
enddefine;
Here is a less wasteful version
define setsize(list) -> num;
if list == [] then
0
elseif member(hd(list), tl(list)) then
setsize(tl(list))
else
1 + setsize(tl(list))
endif -> num
enddefine;
/*
;;; test it
setsize([1 2 3 4 5]) =>
** 5
setsize([5 1 4 2 3 4 5]) =>
** 5
setsize([1 2 3 4 5 5 4 3 2 1]) =>
** 5
*/
define larger(list1, list2) -> boolean;
if setsize(list1) > setsize(list2) then
true
else
false
endif -> boolean
enddefine;
or
define larger(list1, list2) -> boolean;
setsize(list1) > setsize(list2) -> boolean
enddefine;
-- HOWMANY ------------------------------------------------------------
define howmany(list, procedure pred) -> total;
;;; return the number of items in list that satisfy pred
lvars item;
0 -> total;
for item in list do
if pred(item) then total + 1 -> total endif
endfor
enddefine;
/*
howmany([1 a b 2 3 c 4], isword) =>
howmany([1 a 1.5 b 2 3 c 4], isnumber) =>
*/
-- MORE ---------------------------------------------------------------
define more(list1, pred1, list2, pred2);
;;; are there more elements in list1 satisfying pred1 than
;;; elements in list2 satisfying pred2?
howmany(list1, pred1) > howmany(list2, pred2)
enddefine;
-- MOST ---------------------------------------------------------------
We can use <> to join two procedures to produce a new procedure. So if
pred is a predicate which returns true or false as its result, then
using 'pred <> not' we define a new procedure which returns true when
the old one was false and vice versa.
define most(list,pred);
more(list, pred, list, pred <> not)
enddefine;
;;; This version is more efficient. Why?
define most(list,pred);
;;; more than half the elements of the list satisfy pred
2 * howmany(list, pred) > length(list)
enddefine;
/*
most([1 a b 2 3 c 4], isword) =>
most([1 a b 2 3 c 4], isinteger) =>
most([1 a 1.5 b 2 3 c 4], isword) =>
most([1 a 1.5 b 2 3 c 4], isnumber) =>
*/
-- REMOVEALL ----------------------------------------------------------
define removeall(list, pred);
;;; return a list of items in list that do not satisfy pred
findall(list, pred <> not)
enddefine;
or
define removeall(list, procedure pred) -> newlist;
lvars item;
[%for item in list do unless pred(item) then item endunless endfor%]
-> newlist;
enddefine;
/*
removeall([1 a b 2 3 c 4], isword) =>
** [1 2 3 4]
removeall([1 a b 2 3 c 4], isinteger) =>
** [a b c]
removeall([1 a 1.5 b 2 3 c 4], isword) =>
** [1 1.5 2 3 4]
removeall([1 a 1.5 b 2 3 c 4], isnumber) =>
** [a b c]
*/
-- SUBTRACT -----------------------------------------------------------
First version uses partial application, i.e. it partially applies
member to listb, to get the second argument for removall.
define subtract(lista, listb) -> newlist;
;;; return a list of elements of lista that are not in listb
;;; Use partial application for the second argument of removeall
removeall(lista, member(%listb%) ) -> newlist
enddefine;
The next version is more efficient. Why?
define subtract(lista, listb) -> newlist;
;;; return a list of elements of lista that are not in listb
lvars item;
[% for item in lista do
unless member(item, listb) then item endunless
endfor %] -> newlist;
enddefine;
/*
subtract([1 2 3 4 5 6], [2 4 6]) =>
** [1 3 5]
subtract([1 2 3 4 5 6], [1 3 5]) =>
** [2 4 6]
*/
-- OVERLAPS EXCLUDES --------------------------------------------------
define overlaps(list1, list2) -> result;
lvars item;
for item in list1 do
if member(item, list2) then true -> result; return() endif
endfor;
false -> result
enddefine;
define excludes(list1, list2);
not(overlaps(list1,list2))
enddefine;
-- SUBCULTURE ---------------------------------------------------------------
There are VERY MANY ways of doing this. Several different solutions
are provided below.
subculture is given a list of lists and returns a new list of lists
containing the maximal connected subsets from the original list.
I.e. if two lists have any overlapping elements it replaces them
with their union, so the final list of lists contains no lists that
overlap each other.
In general the easiest way to define such a procedure is to deal with
the case where the intput list is empty, and use recursion to deal with
other cases. I.e. if you want to find all the maximal subcultures
in LIST first get the result of finding them for the tail of LIST,
then do what you need to do with the head of LIST.
In this case see if the head of the list overlaps with any lists in the
subculture, and if so extract them and merge them with each other and
the head. The problem as stated in the teach file assumes that the input
list contains only two element lists. So we initially produce a solution
based on that assumption, then see how to generalise it to merge lists
with any number of elements.
-- . subculture version 1
Assume there is a procedure that takes the result of the
recursive call of subculture, and then adds one more two element list.
Call it add_first(first, lists), defined below. We can then use it to
define a recursive version of subculture, thus:
vars procedure add_first; ;;; defined below.
define subculture(lists) -> result;
;;; lists is a list of two element lists. The result must satisfy
;;; the requirements of subculture.
;;; First deal with the trivial case where there are no lists
if lists == [] then
;;; no subcultures in an empty list
[] -> result;
else
;;; There are one or more pairs in lists.
;;; Find all the subcultures in the tail of lists, then
;;; add in the head. That's all!
add_first( hd(lists), subculture(tl(lists)) ) -> result
endif;
enddefine;
;;; Now define add_first. It uses union, defined above.
define add_first(first, lists) -> newlists;
;;; first is a two element list that came from the head of the
;;; list given to subculture. lists is the result of applying
;;; subculture to the tail. So lists already has been arranged
;;; into a list of lists that are "maximal" groups, i.e. they
;;; do not overlap.
;;; If any subculture in lists overlaps with first, merge them.
;;; Since first contains only two elements, and no elements of lists
;;; overlap (by assumption), there cannot be more than two
;;; things to merge.
;;; We grow a new subculture, starting from first, and merge it
;;; with anything in lists that contains an element of first.
lvars newculture;
first -> newculture;
;;; Make a list of all the things in lists that don't contain
;;; either item in the first, and add those that do to newculture
lvars subcult, item1 = first(1), item2 =first(2);
[% for subcult in lists do
if member(item1, subcult) or member(item2, subcult) then
;;; the first and subcult overlap, so
;;; merge the subcult with the newculture
union(newculture, subcult) -> newculture
else
;;; keep the non-overlapping subculture as it is
subcult
endif
endfor
%] -> newlists;
;;; now put the newculture at the front of newlists.
[^ newculture ^^newlists] -> newlists;
enddefine;
-- . Test cases for subculture
/*
;;; test it, with and without tracing subculture.
subculture([]) =>
** []
subculture([[a b] [b c] [d e] [e f]]) =>
** [[a b c] [d e f]]
subculture([[a e] [b f] [c g] [d g]]) =>
** [[a e] [b f] [c d g]]
subculture([[a f] [g i] [h b] [f e] [c h] [j d] [g j] [b h] [d i]]) ==>
** [[a f e] [g j d i] [c b h]]
subculture([[ a b] [a d ] [e f] [d g] [ p q f]]) =>
** [[b a d g] [e p q f]]
;;; Notice that this version does not work when the input includes sets
;;; with more than two elements. Why?
subculture([[a b f ] [c d e] [e b ] [f g] [ a h]])=>
** [[f e b a h] [c d e] [f g]]
;;; It did not notice that e and f each occurred in two of the output
;;; lists. The result should have contained a single big list.
;;; What change is needed to make it notice this? (A very simple
;;; change will fix it. Look at where the program assumed implicitly
;;; that only pairs were involved.)
*/
-- . subculture version 2
The previous version of add_first assumed that first had only two
elements. Thus to check whether an existing subgroup produced by the
recursive call had to be merged with the new one or not it sufficed to
use this test:
if member(item1, subcult) or member(item2, subcult) then
However if first has more than two elements, then instead of
listing them and testing them separately we can use the procedure
overlaps defined above and say
if overlaps(first, subcult) then
overlaps could be defined thus:
define overlaps(list1, list2) -> boolean;
lvars item;
for item in list1 do
returnif( member(item, list2) )( true -> boolean )
endfor;
false -> boolean
enddefine;
;;; Now modified version of add_first
define add_first(first, lists) -> newlists;
;;; We grow a new subculture, starting from first, and merge it
;;; with anything in lists that overlaps with first. Others
;;; are kept as they are
;;; Make a list of all the things in lists that don't contain
;;; either item in the first, and add those that do to first
lvars subcult, item1 = first(1), item2 =first(2);
[% for subcult in lists do
if overlaps(first, subcult) then
union(first, subcult) -> first
else
subcult
endif
endfor
%] -> newlists;
;;; now put the first at the front of newlists.
[^ first ^^newlists] -> newlists;
enddefine;
/*
;;; test that subculture now works with three element lists.
subculture([[a b f ] [c d e] [e b ] [f g] [ a h]])=>
** [[g f d c e b a h]]
*/
-- . subculture version 4
This version is nearly like the above but does not use a separate
add_first procedure. Instead the code is in a loop in subculture.
It also makes use of the procedure "overlaps" defined above
define subculture(lists) -> result;
lvars first, sub, temp;
if lists == [] then
[] -> result;
else
;;; Do the recursive call
subculture(tl(lists)) -> temp;
;;; form a first subculture using the head
hd(lists) -> first;
;;; See if any of the subcultures in temp need to be
;;; merged with the items in first.
[% for sub in temp do
if overlaps(first, sub) then
;;; subculture from tail of list must be merged with first one
union(first, sub) -> first;
else
;;; use the original subculture from tail of list
sub
endif
endfor
%] -> result;
;;; Now add the subculture to the result.
[^first ^^result] -> result;
endif;
enddefine;
/*
;;; test it on the problem that defeated the previous version
subculture([[a b f ] [c d e] [e b ] [f g] [ a h]])=>
** [[g f d c e b a h]]
;;; It works.
;;; You should also test it on the other cases
*/
-- . subculture version 5
;;; Another tempting possible definition of subculture
;;; Start by trying to merge the first item with everything that
;;; can be merged. Then use everything left over to form new
;;; subcultures, recursively. This has a bug. Why?
define subculture(lists) -> result;
lvars item, sub, rest;
if lists == [] then
[] -> result
else
;;; get the first element and see which others can be merged with it
;;; to form a subculture
hd(lists) -> sub;
[] -> rest;
[% for item in tl(lists) do
if overlaps(sub, item) then
union(sub, item) -> sub; ;;; enlarge the subculture
else
item ;;; build a list of elements which
;;; can't be merged with sub
endif;
endfor %] -> rest;
;;; now run subculture on the list of groups that could not
;;; be linked to the first element, and return that with the
;;; enlarged first group
[^sub ^^(subculture(rest))] -> result;
endif;
enddefine;
/*
;;; test it
subculture([[ a b] [a d ] [e f] [d g] [ p q f]]) =>
** [[a b d g] [e p q f]]
;;; That's OK, but here's an example of the bug.
subculture([[a b ] [c d] [e d] [f g] [f a ]])=>
** [[b f a] [c e d] [f g]]
;;; Why has it not merged the first two lists?
*/
-- QUESTION 24 --------------------------------------------------------
This is potentially quite a large mini project, though it is not fully
specified by the question.
An example approach to this can be found in TEACH INDUCE_RULES.P
But it does not solve all the problems.
--- $poplocal/local/teach/sets2.ans
--- Copyright University of Birmingham 1996. All rights reserved. ------Author: Aaron Sloman Feb 1984