TEACH SETS.ANS                                       A.Sloman Feb 1984
                                               Revised A.Sloman Oct 1995

This file has been re-written to conform to the current good practice
regarding programming in Pop-11. In particular, "vars" has been replaced
by "lvars" in many places.

For quick reminders of the syntax of Pop-11 see TEACH POPCORE
A lot more information is given in TEACH PRIMER, also available in
printed form.


                   ILLUSTRATIVE ANSWERS TO TEACH SETS
                    =================================

         CONTENTS - (Use <ENTER> g to access required sections)

 -- ISELEMENT
 -- -- Recursive version of iselement
 -- HAPPYMAN
 -- FINDONE
 -- -- A note on testing for equality using "==" or "="
 -- -- Tests for findone
 -- -- Defining findone
 -- FINDALL
 -- -- Tests for findall
 -- FINDSET
 -- -- Tests for findset

There are many possible ways to define procedures of the sorts specified
in TEACH SETS and TEACH SETS2. So if your answers are different from
those given below, that doesn't mean yours is wrong.

-- ISELEMENT ----------------------------------------------------------

Look back at TEACH SETS for the specification of ISELEMENT

define iselement(item, list) -> result;
    ;;; an 'iterative' version, using a loop
    lvars item, list, result;
    ;;; search down the list looking for a thing = item.
    ;;; if found return true.
    lvars thing;
    for thing in list do
        if item = thing then
            true -> result; return()
        endif;
    endfor;
    ;;; not found, so
    false -> result;
enddefine;

NOTE: we need the call of return() to make sure that when the item is
found the loop stops, i.e. the procedure does not go on searching through
the list. We could not just use quitloop() because then it would do the
instruction after 'endfor', and that would produce a false result.
return() is roughly equivalent to 'goto enddefine', though the latter is
not legal Pop-11.

We need to have 'false -> result' after 'endfor' since if the procedure
gets to the end of the list without finding the item, then that means
the result should be false.

define iselement(item, list)->result;
    lvars item, list, result;
    ;;; Another 'iterative' version, using an "until" loop
    ;;; instead of a "for" loop
    ;;; Is it less clear?
    until list = [] do
        if item = hd(list) then     ;;; or list matches [^item == ]
            true -> result; return()
        else tl(list) -> list
        endif;
    enduntil;
    false -> result;
enddefine;

-- -- Recursive version of iselement

define iselement(item, list) -> result;
    lvars item, list, result;
    ;;; a recursive version
    if list = [] then
        false
    elseif item = hd(list) then     ;;; or list matches [^item == ]
        true
    else iselement(item,tl(list))   ;;; use recursion to search the tail
    endif -> result;
enddefine;

Notice that in this last version we do a single assignment to RESULT
after the conditional expression (if .... endif). We could have done the
assignment in each branch. We are using the fact that the recursive call
will return a true or false result, which will also be the result of the
caller. See TEACH SETS1 for a slightly different format.

Here is a version using MATCHES

define islement(item, list);
    lvars item, list;
    list matches [== ^item ==]
enddefine;

Note that MATCHES will leave a true or false result on the stack.

This version is equivalent, but makes it clearer that a result is
produced:

define islement(item, list) -> result;
    lvars item, list, result;
    list matches [== ^item ==] -> result;
enddefine;


-- HAPPYMAN -----------------------------------------------------------

Test inputs for happyman

vars
    john = [male happy single clerk tall],
    freda = [female happy married surgeon tall],
    tim  = [male sad single butcher short]
;

;;; Use the procedure iselement defined above, or the system procedure
;;; member.

define happyman(list) -> result;
    ;;; list is a description of an object. Check that it contains
    ;;; both the word "happy" and the word "male"
    lvars list, result;
    if iselement("happy", list) then
        if iselement("male", list) then
            true -> result
        else
            false -> result
        endif
    else
        false -> result
    endif;
enddefine;

;;; test it

happyman(john) =>
happyman(freda) =>
happyman(tim) =>
happyman([cow sad milky]) =>

;;; The above definition can be abbreviated

define happyman(list) -> result;
    lvars list, result;
    if iselement("happy", list) and iselement("male", list) then
        true -> result
    else
        false -> result
    endif;
enddefine;

;;; or

define happyman(list) -> result;
    lvars list, result;
    if iselement("happy", list) and iselement("male", list) then
        true
    else
        false
    endif -> result;
enddefine;

;;; or

define happyman(list) -> result;
    lvars list, result;
    iselement("happy",list) and iselement("male", list) -> result
enddefine;

You should make sure that you can see why each of the above has the same
effect. For this you need to understand how conditionals work, how
output locals work, and how the stack works.

See TEACH DEFINE, TEACH STACK.

-- FINDONE -----------------------------------------------------------


-- -- A note on testing for equality using "==" or "="

In the examples that follow, in the definition of findone, I have used
the test

    if list == [] then

instead of

    if list = [] then

because the empty list [] is a unique object, like an integer 66 or a
word "cat" so we do not need the sophistication of the "=" test. The
latter first checks if two things are identical (the very same unique
object, or the very same integer bit pattern), if not, "=" checks to see
if they are of the same type with the same components. So "=" allows two
different strings with the same characters to be the same, whereas "=="
does not:

'the cat' = 'the cat' =>
** <true>

'the cat' == 'the cat' =>
** <false>

With the empty list, an integer or a word only "==" makes sense, and "="
wastes time when the objects are not ==.

If you want to know more about this see HELP * EQUAL


-- -- Tests for findone

;;; test data for findone

vars
    people = [john ethel mary fred],

    john = [male happy single clerk tall],

    ethel = [female unhappy single prime_minister ],

    mary = [female happy married teacher thin tall],

    fred = [male small typist old];

Try the following test examples on the different versions of findone
defined below.

findone("male", people) =>
findone("old", people) =>
findone("male", rev(people)) =>         ;;; searches in reverse order
findone("female", [john fred]) =>
findone("female", [john mary fred]) =>


-- -- Defining findone

;;; These examples use the system procedure member, rather than the
;;; procedure iselement, defined above

define findone(prop, list) -> result;
    ;;; a recursive version
    lvars prop, list, result;
    if list == [] then
        false -> result;
    elseif member(prop, valof(hd(list))) then
        hd(list) -> result
    else
        findone(prop,tl(list)) -> result
    endif
enddefine;


Explanation:
The following line may not be very clear.
    elseif member(prop, valof(hd(list))) then

The crucial thing is to understand the expression:
    member(prop, valof(hd(list)))

which needs to produce a true or false result. In order to do this it
first has to get the arguments for member. The first is the value of
"prop", so that is just left on the stack. Secondly it has to get the
head of list, which may, for instance be the word "john". Then to get
the list associated with the word it does

    valof(hd(list)) ;;; which might be valof("john")

The following might be an easier version to understand, though it is
somewhat longer to write:

define findone(prop,list) -> result;
    lvars
        prop, list, result,
        name, properties;

    if list == [] then
        false -> result;
    else
        hd(list) -> name;
        valof(name) -> properties;
        if member(prop, properties) then
            name -> result
        else
            findone(prop,tl(list)) -> result
        endif
    endif
enddefine;

Probably this iterative version is clearer:

define findone(prop, list) -> result;
    lvars
        prop, list, result,
        name;
    for name in list do
        if member(prop, valof(name)) then
            name -> result;  return()
        endif
    endfor;
    false -> result;
enddefine;

Here is a version using matches (it will be less efficient - why?)

define findone(prop, list) -> result;
    lvars
        prop, list, result,
        name;
    for name in list do
        if valof(name) matches [ == ^prop ==] then
            name -> result;  return()
        endif
    endfor;
    false -> result;
enddefine;


-- FINDALL ------------------------------------------------------------

-- -- Tests for findall

Use the same test data as for findone, above, in these test commands.

findall("male", people) =>
findall("female", people) =>
findall("hungry", people) =>

define findall(prop, list) -> result;
    ;;; prop is any item, though usually a word, as in the above
    ;;;   examples
    ;;; list is a list of variables whose values are lists.
    ;;; search for all the wors whose values contain prop.
    lvars prop, list, result;
    ;;; This is how teach sets suggests you do it recursively
    if list == [] then
        []                              ;;; nothing to go in the result list
    elseif member(prop, valof(hd(list))) then
        ;;; search the rest of the list and combine the result with
        ;;; the first element
        [^(hd(list)) ^^( findall(prop,tl(list)) )]
    else
        ;;; ignore the first element and search the rest of the list
        findall(prop, tl(list))
    endif -> result
enddefine;

Notice that by now the pattern
    member(...., valof( .... ))

has begun to recur. It can therefore be replaced by a procedure.

define hasprop(prop, name);
    lvars prop, name;
    member(prop, valof(name))
enddefine;

So we can re-write the last procedure:

define findall(prop, list) -> result;
    lvars prop, list, result;
    if list = [] then
        []                              ;;; nothing to go in the result list
    elseif hasprop(prop, hd(list)) then
        [^(hd(list)) ^^( findall(prop,tl(list)) )]
    else
        findall(prop, tl(list))
    endif -> result
enddefine;

Make sure you are convinced that this works, by testing it, and tracing
findall, member and hasprop, to see what is going on.

Here is an iterative version

define findall(prop, list) -> result;
    lvars
        prop, list, result,
        item;
    ;;; put all the items satisfying the test on the stack, and then
    ;;; collect them into a list
    [%
      for item in list do
        if hasprop(prop, item) then item endif
      endfor
    %] -> result
enddefine;

-- FINDSET ------------------------------------------------------------

-- -- Tests for findset
;;; use the lists defined above

findset([happy male], people) =>
findset([happy female], people) =>
findset([tall male], people) =>
findset([tall happy ], people) =>
findset([tall happy female], people) =>
findset([tall happy female single], people) =>

We first need a procedure HASALL which will check that something has all
the properties in a given list. This can use the procedure ISSUBSET,
to be defined below:

vars procedure issubset;    ;;; definition coming

define hasall(proplist, name) -> result;
    lvars
        proplist, name, result,
        list;
    valof(name) -> list;    ;;; the actual list of the person's properties
    issubset(proplist, list) -> result;
enddefine;

Now we can define issubset. We take two lists. For each item in the
first list check that it is in the second. If it isn't, then we can
stop: issubset must produce a false result. If we get to the end without
finding such an item in list1, then every item in list1 is in list2. So
the result for issubset must be true. This version uses member instead
of iselement. Thus:


define issubset(list1, list2) -> result;
    lvars
        list1, list2, result,
        item;
    for item in list1 do
        if not(member(item, list2)) then
            false -> result; return();
        endif
    endfor;
    true -> result;
enddefine;

Note the RETURN() which ensures that issubset stops with a false result
should an item from list1 be found which isn't in list2.

Now we can make a procedure which collects all the things which
have all the properties in a given list of properties:

Take each thing in turn: if it has all the properties, then add
it to the list being produced as the result:

define findset(proplist, thinglist) -> result;
    ;;; a recursive version
    lvars proplist, thinglist, result;
    if thinglist = [] then
        []
    elseif hasall(proplist, hd(thinglist)) then
        [^(hd(thinglist)) ^^(findset(proplist, tl(thinglist)) )]
    else
        findset(proplist, tl(thinglist))
    endif -> result
enddefine;

An iterative version which makes a list by putting relevant names on the
stack, using the list brackets  with '%' symbols (see TEACH PERCENT):

define findset(proplist, thinglist) -> result;
    lvars
        proplist, thinglist, result,
        name;
    [%
        for name in thinglist do
            if hasall(proplist, name) then
                name        ;;; just leave the name on the stack
            endif
        endfor
    %]-> result
enddefine;



--- Originally produced at the University of Sussex

--- $poplocal/local/teach/sets.ans
--- The University of Birmingham 1995.  --------------------------------
