Sets

Sets as an abstract data type

We begin with the mathematician's concept of a set: an immutable, unordered collection of distinct values, possibly empty. As usual, we assume that all of the elements of a set are of the same data type, which is part of the definition of the type of the set.

In mathematical set theory, it is axiomatic that the empty set -- the set containing no members -- exists. The abstract data type should include a name for the empty set:

the-empty-set, the set that has no members.

The axioms of the most widely accepted set theories also assert the existence of any set containing one or two elements. The abstract data type should provide constructors for such sets:

create-singleton
Input: member, a value.
Output: singleton, a set of which the members are of the same type as member.
Preconditions: none.
Postconditions: member is a member of singleton and singleton has no other members.

create-doubleton
Inputs: first-member and second-member, values of the same type.
Output: doubleton, a set of which the members are of the same type as first-member and second-member.
Preconditions: none.
Postconditions: first-member is a member of doubleton, second-member is a member of doubleton, and doubleton has no other members.

In some axiomatizations of set theory, there is a ``universal'' set that contains every value. But, depending on the type of the elements, it is not obvious that any such set exists, and in addition it may be extremely difficult to implement such sets as the set of all values of type Real or of type Natural (using bignums). We shall avoid these problems by not positing a universal set.

The most fundamental operation on existing sets is the membership test -- determining whether a given value is an element of a given set:

member
Inputs: candidate, a value, and sett, a set of which the members are of the same type as candidate.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if candidate is a member of sett and false if it is not.

It should be possible to form the union and intersection of any two sets:

union
Inputs: left-operand and right-operand, both sets, with members of the same type.
Output: result, a set of which the members are of the same type as those of left-operand and right-operand.
Preconditions: none.
Postconditions: Every member of left-operand is a member of result. Every member of right-operand is a member of result. Every element of result is either a member of left-operand or a member of right-operand or both.

intersection
Inputs: left-operand and right-operand, both sets, with members of the same type.
Output: result, a set of which the members are of the same type as those of left-operand and right-operand.
Preconditions: none.
Postconditions: Every member of result is a member of left-operand. Every member of result is a member of right-operand. Every value that is both a member of left-operand and a member of right-operand is a member of result.

Some axiomatizations of set theory provide for a complementation operation constructs the set of all values that are not members of a given set. Other theorists hold that this unbounded operation, like the universal set, is incoherent and unnecessary. Moreover, it is often difficult to implement in a satisfactory way. So in this abstract data type we shall instead use the operations of relative complementation and sundering, which return only subsets of a given set. Relative complementation gives the set of all members of one set that are not also members of another set; this is the ``set difference'' operation for which Pascal uses the - operator. Sundering is a generalization of this idea to give the set of all members of a set that meet some condition that is expressed as a Boolean function.

relative-complement
Inputs: left-operand and right-operand, both sets, with members of the same type.
Output: result, a set of which the members are of the same type as those of left-operand and right-operand.
Preconditions: none.
Postconditions: Every member of result is a member of left-operand. No member of result is a member of right-operand. Every value that is a member of left-operand but not of right-operand is a member of result.

sunder
Inputs: sett, a set, and test, an operation that takes one input, a value of the same type as the members of sett, and yields out output, a Boolean.
Output: result, a set of which the members are of the same type as those of sett.
Preconditions: none.
Postconditions: Every member of result is a member of sett. Applying test to any member of result yields true; applying it to any member of sett that is not a member of result yields false.

The special cases of union and relative-complement in which the right operand is a singleton occur often enough to justify separate operations:

adjoin
Inputs: sett, a set, and adjunct, a value of the same type as the members of sett.
Output: result, a set of which the members are of the same type as those of sett.
Preconditions: none.
Postconditions: Every member of sett is a member of result. adjunct is a member of result. Every member of result, other than adjunct, is a member of sett.

disjoin
Inputs: sett, a set, and disjunct, a value of the same type as the members of sett.
Output: result, a set of which the members are of the same type as those of sett.
Preconditions: none.
Postconditions: Every member of result is a member of sett. disjunct is not a member of result. Every member of sett, other than disjunct, is a member of result.

It is sometimes useful to determine how many members a set has:

cardinality
Input: operand, a set.
Output: result, an integer.
Preconditions: none.
Postcondition: result is the number of members of operand.

It is also useful to be able to test whether a set has any members at all:

empty
Input: operand, a set.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if operand has no members and false if it has one or more.

There are two common relations between sets: equality, in the sense of having exactly the same members, and the subset relation:

equal
Inputs: left-operand and right-operand, both sets, with members of the same type.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if every member of left-operand is a member of right-operand and vice versa, false otherwise.

subset
Inputs: left-operand and right-operand, both sets, with members of the same type.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if every member of left-operand is a member of right-operand, false otherwise.

In set theory, the existence of the power set (set of subsets) of any given set is usually postulated, so the abstract data type should provide an operation to construct power sets:

power-set
Input: operand, a set.
Output: result, a set of which the members are sets of which the members are of the same type as the members of operand.
Preconditions: none.
Postconditions: Every subset of operand is a member of result, and every member of result is a subset of operand.

Finally, we need some way of selecting and operating on the members of a given set. Since the members of a set are not ordered, the operation of selecting a member of a set is non-deterministic, in the sense that there's no way to count on getting a particular one; this is reflected in the weak postcondition of the following operation:

select
Input: operand, a set.
Outputs: selected, a member of the same type as the members of operand, and remainder, a set of which the members are again of that type.
Preconditions: operand is not the empty set.
Postconditions: selected is a member of operand. Every member of remainder is a member of operand. selected is not a member of remainder. Every member of operand, except selected, is a member of remainder.

Sometimes it is possible to avoid the trouble of taking a set apart with successive select operations by applying a higher-order operation on the set as a whole:

map
Inputs: sett, a set, and mapper, an operation that takes one input, a value of the same type as the members of sett, and yields one output.
Output: result, a set of which the members are of the type of the output of mapper.
Preconditions: none.
Postconditions: The result of applying mapper to any member of sett is a member of result. Every member of result is the result of applying mapper to some member of sett.

apply-to-each
Inputs: sett, a set, and applicand, an operation that takes one input, a value of the same type as the members of sett, and yields no outputs.
Outputs: none.
Preconditions: none.
Postconditions: applicand has been applied exactly once to each member of sett.

every-member
Inputs: sett, a set, and test, an operation that takes one input, a value of the type of the members of sett, and yields one output, a Boolean.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is false if there is a member e of sett such that applying test to e yields false. Otherwise, result is true.

some-member
Inputs: sett, a set, and test, an operation that takes one input, a value of the same type as the members of sett, and yields one output, a Boolean.
Output: result, a Boolean.
Preconditions: none.
Postconditions: result is true if there is a member e of sett such that applying test to e yields true. Otherwise, result is false.

A Sets module in HP Pascal

The set types in standard Pascal clearly fall considerably short of supplying a satisfactory implementation of this abstract data type. Members of Pascal sets must belong to some ordinal type, often (as in HP Pascal) limited to a maximum cardinality of 256 members. The empty set and singleton and doubleton constructors are available, the in operator implements member, and +, *, -, =, and <= implement union, intersection, relative-complement, equal, and subset, respectively. But Pascal does not provide any version of cardinality, select, or any of the higher-order operations, and implementing them in standard Pascal usually means running through every possible value of the base type of the set to determine whether it is in the set operand of the operation.

A better approach is to use a dynamically allocated structure of some sort. As Walker points out in the textbook (page 233), the set operations are much more efficient if one can assume that the elements of the data type, though not regarded as ordered at the level of sets, can be arranged inexpensively in some linear order at the level of implementation. I have assumed that, in addition to an equality test EqualElement, the Element module provides a Boolean function PrecedesElement that determines whether one value of the type precedes another in such a linear ordering (which may be entirely arbitrary or conventional).

Walker suggests implementing sets as singly-linked lists, consistently stored in ascending order (that is, so that PrecedesElement would return true for each adjacent pair of elements). I've chosen to illustrate instead what an implementation using binary search trees looks like. Instead of importing the binary search tree operations from a module, I wrote them into the implements section of the Sets module, mainly because I needed to adapt a couple of them to this particular application and to add a couple of operations that would not usually be provided in a BinarySearchTrees module. However, I've observed an implicit abstraction barrier: None of the set operations refers in any way to the internal structure of binary search trees, which are accessed only through the operations provided for them. It would be straightforward to substitute a different implementation of binary search trees (for instance, using self-balancing red-black trees).

Because of Pascal's strong typing rules, I've provided only sets of type Element in this package. This means that the map operation is constrained to accept, as values for the mapper operand, only functions that yield Element values. It also implies that the powerset operation is not implemented.

Since the set type defined in this module is dynamically allocated, I supplied a deallocation procedure and an assignment operation that actually copies the set instead of simply duplicating the pointer.

Set is a reserved word in Pascal, so I've used Congeries as the identifier denoting the type.

{ This module defines an interface for a set data type and implements it
  for HP 9000 Series 700 workstations under HP-UX 9.x, using HP Pascal. 

  Programmer: John Stone, Grinnell College.
  Original version: November 26, 1996
}

$heap_dispose on$

module Sets;

$search 'set-elements.o'$
import Elements;

export

  type
    Congeries = ^SetRecord;         { an opaque type }

  { TheEmptySet is a constant denoting the set with no members. }

  const
    TheEmptySet = nil;

  { The CreateSingletonSet function constructs and returns a set containing
    just the one element specified by its argument. }

  function CreateSingletonSet (Member: Element): Congeries;

  { The CreateDoubletonSet function constructs and returns a set containing
    each of the elements specified by its arguments, but nothing more. }

  function CreateDoubletonSet (FirstMember, SecondMember: Element):
    Congeries;

  { The MemberOfSet function determines whether a given element is actually
    a member of a given set. }

  function MemberOfSet (Candidate: Element; Sett: Congeries): Boolean;

  { The UnionOfSets function constructs and returns a set containing all
    of the elements in the sets specified by its arguments, but nothing
    more. }

  function UnionOfSets (LeftOperand, RightOperand: Congeries): Congeries;

  { The IntersectionOfSets function constructs and returns a set containing
    all of the elements common to the sets specified by its arguments, but
    nothing more. }

  function IntersectionOfSets (LeftOperand, RightOperand: Congeries):
    Congeries;

  { The RelativeComplementOfSets function constructs and returns a set
    containing all of the members of the set specified by its first
    argument that are not also members of the set specified by its
    second argument, but nothing more. }

  function RelativeComplementOfSets (LeftOperand, RightOperand: Congeries):
    Congeries;

  { The SunderSet function constructs and returns a set containing all of
    the members of the set specified by its first argument that meet the
    condition expressed by its second argument, in the sense that when
    applied to such an element the second argument returns True. }

  function SunderSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Congeries;

  { The AdjoinToSet function constructs and returns a set containing all
    of the members of the set specified by its first argument and also
    the element specified by its second argument.  If the latter is already
    a member of the former, AdjoinToSet returns a freshly allocated copy
    of the former. }

  function AdjoinToSet (Sett: Congeries; Adjunct: Element): Congeries;

  { The DisjoinFromSet function constructs and returns a set containing all
    of the members of the set specified by its first argument except for
    the element specified by its second argument.  If the latter is not
    a member of the former, DisjoinFromSet returns a freshly allocated
    copy of the former. }

  function DisjoinFromSet (Sett: Congeries; Disjunct: Element): Congeries;

  { The CardinalityOfSet function determines and returns the number of
    members in a given set. }

  function CardinalityOfSet (Operand: Congeries): Integer;

  { The EmptySet function determines whether its argument has any members,
    returning True if it has none and False if it has one or more. }

  function EmptySet (Operand: Congeries): Boolean;

  { The EqualSets function determines whether its arguments are equal as
    sets -- that is, whether every member of the first is a member of the
    second, and vice versa. }

  function EqualSets (LeftOperand, RightOperand: Congeries): Boolean;

  { The Subset function determines whether every member of the set
    specified by its first argument is also a member of the set specified
    by its second argument, returning True if so and False if not. }

  function Subset (LeftOperand, RightOperand: Congeries): Boolean;

  { The SelectFromSet procedure chooses one member of a given non-empty
    set and returns it through the variable-parameter Selected.  It also
    constructs a set containing all of the other members of the given set
    and returns it through the variable-parameter Remainder. }

  procedure SelectFromSet (Operand: Congeries; var Selected: Element;
    var Remainder: Congeries);

  { The MapSet function applies a given function to each member of a given
    set and returns a set containing all of the results. }

  function MapSet (Sett: Congeries;
    function Mapper (Operand: Element): Element): Congeries;

  { The ApplyToEachMemberOfSet procedure applies a given procedure to each
    member of a given set. }

  procedure ApplyToEachMemberOfSet (Sett: Congeries;
    procedure Applicand (Operand: Element));

  { The EveryMemberOfSet function determines whether all of the members
    of a given set have the property expressed by its second argument, in
    the sense that applying the second argument to any such member would
    yield True. }

  function EveryMemberOfSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Boolean;

  { The SomeMemberOfSet function determines whether at least one of the
    members of a given set has the property expressed by its second
    argument, in the sense that applying the second argument to such a
    member would yield True. }

  function SomeMemberOfSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Boolean;

  { The AssignSet procedure constructs and returns, though the
    variable-parameter Target, a freshly allocated copy of the given
    set Source. }

  procedure AssignSet (var Target: Congeries; Source: Congeries);

  { The DeallocateSet procedure recycles all of the storage associated
    with a given set. }

  procedure DeallocateSet (var Delend: Congeries);

implement

  import
    StdErr;

  { The first few definitions set up the exception handler for
    the Sets module. }

  const
    FirstSetExceptionCode = 1;
    SelectFromSetException = 1;
    SetExceptionException = 2;
    LastSetExceptionCode = 2;

  { In this implementation, sets are represented as binary search trees. }

  type
    BinarySearchTree = Congeries;
    SetRecord = record
                  Datum: Element;
                  Left, Right: BinarySearchTree
                end;

  { Here is the exception handler for sets. }

  procedure SetExceptionHandler (ExceptionCode: Integer);
  begin
    if (ExceptionCode < FirstSetExceptionCode) or
                        (LastSetExceptionCode < ExceptionCode) then
      ExceptionCode := SetExceptionException;
    WriteLn (StdErr, 'Exception #', ExceptionCode : 1,
             ' in module Sets:');
    case ExceptionCode of
    SelectFromSetException:
      WriteLn (StdErr, 'An empty set was passed to the SelectFromSet ',
               'function.');
    SetExceptionException:
      WriteLn (StdErr, 'An unknown exception code was passed to the ',
               'SetExceptionHandler procedure.')
    end
  end;
 
  { Here are the definitions relating to binary search trees.  Deletion is
    not needed in this case, since the set operations construct all their
    results from scratch.  However, a function has been added to return
    (without removing) the element at the root of a non-empty binary
    search tree, and another function to determine whether every element
    in a binary search tree meets a specified condition. }

  function MakeEmptyBinarySearchTree: BinarySearchTree;
  begin
    MakeEmptyBinarySearchTree := nil
  end;

  function MakeSingletonBinarySearchTree (Elm: Element): BinarySearchTree;
  var
    Result: BinarySearchTree;
  begin
    New (Result);
    Result^.Datum := Elm;
    Result^.Left := MakeEmptyBinarySearchTree;
    Result^.Right := MakeEmptyBinarySearchTree;
    MakeSingletonBinarySearchTree := Result
  end;

  function EmptyBinarySearchTree (B: BinarySearchTree): Boolean;
  begin
    EmptyBinarySearchTree := (B = nil)
  end;

  { Note that the insertion procedure discards duplicate elements
    instead of storing them in the right subtree. }

  procedure InsertIntoBinarySearchTree (Elm: Element;
    var B: BinarySearchTree);
  begin
    if EmptyBinarySearchTree (B) then
      B := MakeSingletonBinarySearchTree (Elm)
    else if PrecedesElement (Elm, B^.Datum) then
      InsertIntoBinarySearchTree (Elm, B^.Left)
    else if PrecedesElement (B^.Datum, Elm) then
      InsertIntoBinarySearchTree (Elm, B^.Right)
  end;

  function SearchBinarySearchTree (Sought: Element; B: BinarySearchTree):
    Boolean; 
  begin
    if EmptyBinarySearchTree (B) then
      SearchBinarySearchTree := False
    else if PrecedesElement (Sought, B^.Datum) then
      SearchBinarySearchTree := SearchBinarySearchTree (Sought, B^.Left)
    else if PrecedesElement (B^.Datum, Sought) then
      SearchBinarySearchTree := SearchBinarySearchTree (Sought, B^.Right)
    else
      SearchBinarySearchTree := True
  end;

  { It is a precondition of the ElementAtRootOfBinarySearchTree procedure
    that its argument is not nil.  Caller beware! }

  function ElementAtRootOfBinarySearchTree (B: BinarySearchTree):
    Element;
  begin
    ElementAtRootOfBinarySearchTree := B^.Datum
  end;

  { The ApplyThroughoutBinarySearchTree procedure performs a preorder
    traversal rather than an inorder traversal in this case, so that
    the members of a set are not encountered in ascending order (which
    would have a devastating effect on such operations as AssignSet
    and IntersectionOfSets -- they would return trees with linear
    structure even when given balanced trees as inputs). }

  procedure ApplyThroughoutBinarySearchTree (B: BinarySearchTree;
    procedure P (Elm: Element));
  begin
    if not EmptyBinarySearchTree (B) then begin
      P (B^.Datum);
      ApplyThroughoutBinarySearchTree (B^.Left, P);
      ApplyThroughoutBinarySearchTree (B^.Right, P)
    end
  end;

  { The EveryElementOfBinarySearchTree function determines whether every
    element of a binary search tree meets a condition specified by the
    Boolean function Test.  It basically performs a preorder traversal
    of the tree, backing out (and returning False) as soon as it
    encounters an element that does not meet the test condition or
    returning True at the end of the process if no such element is
    encountered. }

  function EveryElementOfBinarySearchTree (B: BinarySearchTree;
    function Test (Elm: Element): Boolean): Boolean;
  begin
    if B = nil then
      EveryElementOfBinarySearchTree := True
    else if not Test (B^.Datum) then
      EveryElementOfBinarySearchTree := False
    else if EveryElementOfBinarySearchTree (B^.Left, Test) then
      EveryElementOfBinarySearchTree :=
                        EveryElementOfBinarySearchTree (B^.Right, Test)
    else
      EveryElementOfBinarySearchTree := False
  end;

  procedure DeallocateBinarySearchTree (var B: BinarySearchTree);
  begin
    if not EmptyBinarySearchTree (B) then begin
      DeallocateBinarySearchTree (B^.Left);
      DeallocateBinarySearchTree (B^.Right);
      Dispose (B)
    end
  end;

  { The exported functions and procedures begin here. }

  function CreateSingletonSet (Member: Element): Congeries;
  var
    Result: BinarySearchTree;
  begin
    Result := MakeEmptyBinarySearchTree;
    InsertIntoBinarySearchTree (Member, Result);
    CreateSingletonSet := Result
  end;

  function CreateDoubletonSet (FirstMember, SecondMember: Element):
    Congeries;
  var
    Result: BinarySearchTree;
  begin
    Result := MakeEmptyBinarySearchTree;
    InsertIntoBinarySearchTree (FirstMember, Result);
    InsertIntoBinarySearchTree (SecondMember, Result);
    CreateDoubletonSet := Result
  end;

  function MemberOfSet (Candidate: Element; Sett: Congeries): Boolean;
  begin
    MemberOfSet := SearchBinarySearchTree (Candidate, Sett)
  end;

  function UnionOfSets (LeftOperand, RightOperand: Congeries): Congeries;
  var
    Result: BinarySearchTree;

    procedure AdjoinToResult (Elm: Element);
    begin
      InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function UnionOfSets }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (LeftOperand, AdjoinToResult);
    ApplyThroughoutBinarySearchTree (RightOperand, AdjoinToResult);
    UnionOfSets := Result
  end;

  function IntersectionOfSets (LeftOperand, RightOperand: Congeries):
    Congeries;
  var
    Result: BinarySearchTree;

    procedure ConditionallyAdjoinToResult (Elm: Element);
    begin
      if SearchBinarySearchTree (Elm, RightOperand) then
        InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function IntersectionOfSets }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (LeftOperand,
                                     ConditionallyAdjoinToResult);
    IntersectionOfSets := Result
  end;

  function RelativeComplementOfSets (LeftOperand, RightOperand: Congeries):
    Congeries;
  var
    Result: BinarySearchTree;

    procedure ConditionallyAdjoinToResult (Elm: Element);
    begin
      if not SearchBinarySearchTree (Elm, RightOperand) then
        InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function RelativeComplementOfSets }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (LeftOperand,
                                     ConditionallyAdjoinToResult);
    RelativeComplementOfSets := Result
  end;

  function SunderSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Congeries;
  var
    Result: BinarySearchTree;

    procedure ConditionallyAdjoinToResult (Elm: Element);
    begin
      if Test (Elm) then
        InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function SunderSet }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (Sett, ConditionallyAdjoinToResult);
    SunderSet := Result
  end;

  function AdjoinToSet (Sett: Congeries; Adjunct: Element): Congeries;
  var
    Result: BinarySearchTree;

    procedure AdjoinToResult (Elm: Element);
    begin
      InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function AdjoinToSet }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (Sett, AdjoinToResult);
    InsertIntoBinarySearchTree (Adjunct, Result);
    AdjoinToSet := Result
  end;

  function DisjoinFromSet (Sett: Congeries; Disjunct: Element): Congeries;
  var
    Result: BinarySearchTree;

    procedure ConditionallyAdjoinToResult (Elm: Element);
    begin
      if not EqualElement (Elm, Disjunct) then
        InsertIntoBinarySearchTree (Elm, Result)
    end;

  begin { function DisjoinFromSet }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (Sett, ConditionallyAdjoinToResult);
    DisjoinFromSet := Result
  end;

  function CardinalityOfSet (Operand: Congeries): Integer;
  var
    Result: Integer;

    procedure TallyElement (Elm: Element);
    begin
      Result := Result + 1
    end;

  begin { function Cardinality }
    Result := 0;
    ApplyThroughoutBinarySearchTree (Operand, TallyElement);
    CardinalityOfSet := Result
  end;

  function EmptySet (Operand: Congeries): Boolean;
  begin
    EmptySet := EmptyBinarySearchTree (Operand)
  end;

  function EqualSets (LeftOperand, RightOperand: Congeries): Boolean;
  begin
    if Subset (LeftOperand, RightOperand) then
      EqualSets := Subset (RightOperand, LeftOperand)
    else
      EqualSets := False
  end;

  function Subset (LeftOperand, RightOperand: Congeries): Boolean;

    function MemberOfRightOperand (Elm: Element): Boolean;
    begin
      MemberOfRightOperand := SearchBinarySearchTree (Elm, RightOperand)
    end;

  begin { function Subset }
    Subset := EveryMemberOfSet (LeftOperand, MemberOfRightOperand)
  end;

  procedure SelectFromSet (Operand: Congeries; var Selected: Element;
    var Remainder: Congeries);
  begin
    Assert (not EmptySet (Operand), SelectFromSetException,
            SetExceptionHandler);
    Selected := ElementAtRootOfBinarySearchTree (Operand);
    Remainder := DisjoinFromSet (Operand, Selected)
  end;

  function MapSet (Sett: Congeries;
    function Mapper (Operand: Element): Element): Congeries;
  var
    Result: Congeries;

    procedure AdjoinMapOutputToResult (Elm: Element);
    begin
      InsertIntoBinarySearchTree (Mapper (Elm), Result)
    end;

  begin { function MapSet }
    Result := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (Sett, AdjoinMapOutputToResult);
    MapSet := Result
  end;

  procedure ApplyToEachMemberOfSet (Sett: Congeries;
    procedure Applicand (Operand: Element));
  begin
    ApplyThroughoutBinarySearchTree (Sett, Applicand)
  end;

  function EveryMemberOfSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Boolean;
  begin
    EveryMemberOfSet := EveryElementOfBinarySearchTree (Sett, Test)
  end;

  function SomeMemberOfSet (Sett: Congeries;
    function Test (Operand: Element): Boolean): Boolean;

    function Complement (Operand: Element): Boolean;
    begin
      Complement := not Test (Operand)
    end;

  begin { function SomeMemberOfSet }
    SomeMemberOfSet :=
                not EveryElementOfBinarySearchTree (Sett, Complement)
  end;

  procedure AssignSet (var Target: Congeries; Source: Congeries);

    procedure AdjoinToTarget (Elm: Element);
    begin
      InsertIntoBinarySearchTree (Elm, Target)
    end;

  begin { procedure AssignSet }
    Target := MakeEmptyBinarySearchTree;
    ApplyThroughoutBinarySearchTree (Source, AdjoinToTarget)
  end;

  procedure DeallocateSet (var Delend: Congeries);
  begin
    DeallocateBinarySearchTree (Delend)
  end;

end.

This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/courses/fundamentals/sets.html

created November 26, 1996
last revised November 26, 1996