Search                        Top                                  Index
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. ------