{$APPTYPE Console}
program Collections;

const
  MaxCount = 100;

type
  { abstrakte Klasse}
  tAny = class
         end;

  { abstrakte Klasse fuer Elemente einer Menge, d.h. von dieser
    Klasse können keine Instanzen gebildet werden. Sie dient
    nur zum Definieren der wirklich benoetigten Klassen. Sinn
    dieses Vorgehens ist, die Eigenschaften, die alle weiteren
    Klassen ohnehin in gleicher oder ähnlicher Form  benoetigen
    hier genau einmal zu implementieren, anstatt sie in den
    weiteren Implementierungen mehrfach zu schreiben }
  tElement = class(tAny)
             public
               function Clone: tElement; virtual; abstract;
               function GetTypeId: pointer; virtual; abstract;
               function IsEqualP(rE: tElement): boolean; virtual; abstract;
               function GetAsString: string; virtual; abstract;
             end;

  tIntElement = class(tElement)
                private
                  TypeId : integer;
                  Data   : integer;
                public
                  constructor Create(I: integer);
                  function Clone: tElement; override;
                  function GetTypeId: pointer; override;
                  function IsEqualP(rE: tElement): boolean; override;
                  function GetAsString: string; override;
                end;

  tRealElement = class(tElement)
                 private
                   Data : real;
                 public
                   function Clone: tElement; override;
                   constructor Create(R: real);
                   function GetTypeId: pointer; override;
                   function IsEqualP(rE: tElement): boolean; override;
                   function GetAsString: string; override;
                 end;

  { abstrakte Klasse fuer "Anhaeufungen" von Elementen, wie Mengen,
    Stringlisten, Vektoren etc.. Dieses Vorgehen ermoeglicht bei
    Ergaenzungen und Erweiterungen die umfassend gueltigen Codeteile
    durch eine Vererbungsoperation einfach weiter zu nutzen. Natuerlich
    koennte man die hier enthaltenen Daten und Operationen auch in die
    Klasse Set einfuegen, dies ginge technisch, waere aber wenig
    objektorientiert }
  tCollection = class(tAny)
                protected
                  Count    : integer;
                  Elements : array[0..MaxCount] of tElement;
                public
                  constructor Create;
                  destructor Done;
                  procedure Clear;
                  function GetElementCount: integer;
                  function GetElement(I: integer): tElement;
                  procedure Insert(rE: tElement); virtual;
                  function IsInP(rE: tElement): boolean;
                  procedure Remove(rE: tElement);
                  function GetAsString: string;
                end;

  { die eigentliche Mengenklasse, die dank objektorientiertem
    Unterbau nur Operationen enthält, die dem mathmatischen Begriff
    einer Menge entspricht }
  tSet = class(tCollection)
         public
           constructor Create;
           procedure Insert(rE: tElement); override;
           procedure Difference(var S1, S2: tSet);
           procedure Intersection(var S1, S2: tSet);
           procedure Union(var S1, S2: tSet);
         end;


{............................................................................}
{    Globales Error-Handling, etwas unsauber, weil nicht objektorientiert    }
{............................................................................}

procedure ErrorMsg(Msg: string);
begin
  writeln(Msg);
  readln;
  halt;
end;

{............................................................................}
{                      Implementierung der Integer-Klasse                    }
{............................................................................}

function tIntElement.Clone : tElement;
begin
  Clone := tIntElement.Create(Data);
end;

constructor tIntElement.Create(I : integer);
begin
  Data := I;
end;

function tIntElement.GetTypeId: pointer;
var tIntElementTypeId : integer;
begin
  GetTypeId := addr(TIntElementTypeId);
end;


function tIntElement.IsEqualP(rE : tElement) : boolean;
var rI : tIntElement;
begin
  if rE.GetTypeId <> self.GetTypeId
  then IsEqualP := false
  else begin
         rI       := tIntElement(rE);
         IsEqualP := (Data = rI.Data);
       end;
end;

function tIntElement.GetAsString : string;
var S : string;
begin
  str(Data, S);
  GetAsString := S;
end;

{............................................................................}
{                      Implementierung der Real-Klasse                       }
{............................................................................}

function tRealElement.Clone : tElement;
begin
  Clone := tRealElement.Create(Data);
end;

constructor tRealElement.Create(R: real);
begin
  Data := R;
end;

function tRealElement.GetTypeId: pointer;
var TRealElementTypeId : integer;
begin
  GetTypeId := addr(TRealElementTypeId);
end;

function tRealElement.IsEqualP(rE : tElement) : boolean;
var rR : tRealElement;
begin
  if rE.GetTypeId <> self.GetTypeId
  then IsEqualP := false
  else begin
         rR       := tRealElement(rE);
         IsEqualP := (Data = rR.Data);
       end;
end;


function tRealElement.GetAsString : string;
var S: string;
begin
  str(Data, S);
  GetAsString := S;
end;

{............................................................................}
{     Implementierung einer Klasse fuer mehrelementige Ansammlungen          }
{............................................................................}

{ Eine Collection aufraeumen }
procedure tCollection.Clear;
begin
  while Count > 0 do
  begin
    dec(Count);
    Elements[Count].free;
  end;
end;

{ Eine Collection erzeugen }
constructor tCollection.Create;
{ Realisierung (hier etwas untypisch): Setzen des Elemente-Zaehlers auf 0 }
begin
  Count := 0;
end;

{ Eine Collection beseitigen, hier gleichbedeutend mit
  'aufraeumen', da als array und nicht dynamisch
  realisiert }
destructor tCollection.Done;
begin
  Clear;
end;

{ Die Anzahl der Elemente ausgeben }
function tCollection.GetElementCount : integer;
begin
  GetElementCount := Count;
end;

{ Zugriff auf das Element an der Stelle i }
function tCollection.GetElement(I : integer) : tElement;
begin
  GetElement := Elements[I];
end;

{ Ein Element einfuegen}
procedure tCollection.Insert(rE : tElement);
begin
  if (Count + 1 = MaxCount)
  then ErrorMsg('tCollection.Insert : overflow');
  Elements[Count] := rE;
  inc(Count);
end;

{ Die Anwesenheit eines Elements ermitteln }
function tCollection.IsInP(rE : tElement): boolean;
var I : integer;
begin
  for I := 0 to Count - 1 do
  begin
    if Elements[I].IsEqualP(rE)
    then begin
           IsInP := true;
           exit;
         end;
  end;
  IsInP := false;
end;

{ Ein Element loeschen }
procedure tCollection.Remove(rE : tElement);
var I, J: integer;
begin
  I := 0;
  while (I < Count) do                         { Element rE suchen          }
  begin
    if Elements[I].IsEqualP(rE)
    then begin                                 { gefunden    =>   entfernen }
           for J := I to Count - 2 do          { d.h.: alles um 1 nach links}
               Elements[J] := Elements[J + 1];
           dec(Count);                         { ...und Anzahl um 1 reduz.  }
         end;
  inc(I);
  end;
end;

{ Alle Elemente einer Collection ausgeben }
function tCollection.GetAsString : string;
var
  Count1, I : integer;
  S         : string;
begin
  Count1 := Count - 1;
  for I := 0 to Count1 do
  begin
    if I > 0                                   { das Unmoegliche abfangen   }
    then  S := S + ', ';                       { den String aufaeuffeln     }
    S := S + GetElement(I).GetAsString;
  end;
  GetAsString := S;
end;

{............................................................................}
{                  Implementierung einer Klasse fuer Mengen im               }
{............................................................................}

{ Eine Menge 'eroeffnen'}
constructor tSet.Create;
begin
  inherited Create;
end;

{ Einfügen eines Elements in die Menge }
procedure tSet.Insert(rE : tElement);
begin
  if IsInP(rE)
  then exit;                     { in einer Menge dürfen keine Duplikate sein }
  inherited Insert(rE);          { ansonsten erfuellt d. 'UrInsert' den Zweck }
end;

{ alle Elemente aus S1, die nicht in S2 enthalten sind }
procedure tSet.Difference(var S1, S2 : tSet);
var
  Count1, I : integer;
  rE        : tElement;
begin
  Clear;
  Count := S1.GetElementCount - 1;
  for I := 0 to Count1 do        { wiederhole mit allen Elementen ...        }
  begin
    rE := S1.GetElement(I);      {   nehme ein Element aus S1                }
    if not S2.IsInP(rE)          {   und teste, ob es in S2 enthalten ist    }
    then Insert(rE.Clone);       {   wenn nein, dann in die Menge packen     }
  end;
end;

{ Elemente, die sowohl in S1 als auch in S2 enthalten sind }
procedure tSet.Intersection(var S1, S2 : tSet);
var
  Count1, I : integer;
  rE        : tElement;
begin
  Clear;
  Count1 := S1.GetElementCount - 1;
  for I := 0 to Count1 do        { wiederhole mit allen Elementen ...        }
  begin
    rE := S1.GetElement(I);      {   nehme ein Element aus S1                }
    if S2.IsInP(rE)              {   und teste, ob es in S2 enthalten ist    }
    then Insert(rE.Clone);       {   wenn ja, dann in die Menge S packen     }
  end;
end;

{ Elemente, die in S1 oder in S2 enthalten sind }
procedure tSet.Union(var S1, S2: tSet);
var
  Count1, I : integer;
  rE        : tElement;
begin
  Clear;
  Count1 := S1.GetElementCount - 1;
  for I := 0 to Count1 do            { wiederhole mit allen Elementen aus S1 }
      Insert(S1.GetElement(I).Clone);{   fuege sie in die Menge S ein        }
  Count1 := S2.GetElementCount - 1;
  for I := 0 to Count1 do            { wiederhole mit allen Elementen aus S2 }
      Insert(S2.GetElement(I).Clone);{   fuege sie in die Menge S ein        }
end;

{............................................................................}
{                 ... und das Ganze anwenden ...                             }
{............................................................................}

var
  S, S1, S2 : tSet;

begin
  S1 := tSet.Create;                          { Bilden der Menge S1     }
  S1.Insert(tIntElement.Create(1));           { Einfügen von Elementen  }
  S1.Insert(tIntElement.Create(3));           { ... }
  S1.Insert(tIntElement.Create(5));           { ... }
  S1.Insert(tRealElement.Create(6.0));        { ... }

  S2 := tSet.Create;                          { Bilden der Menge S2     }
  S2.Insert(tIntElement.Create(2));           { Einfügen von Elementen  }
  S2.Insert(tIntElement.Create(3));           { ... }
  S2.Insert(tIntElement.Create(4));           { ... }
  S2.Insert(tRealElement.Create(6.0));        { ... }

  S := tSet.Create;                           { Bilden der Menge S      }

  S.Union(S1, S2);                            { Vereinigung bilden      }
  writeln('Vereinigung: ',  S.GetAsString);   { Out: 1, 2, 3, 4, 5, 6.0 }

  S.Intersection(S1, S2);                     { Schnittmenge bilden     }
  writeln('Durchschnitt: ', S.GetAsString);   { Out: 6.0                }

  S.Difference(S1, S2);                       { Differenz bilden        }
  writeln('Differenz: ',    S.GetAsString);   { Out: 1, 3, 5            }

  readln;
end.