       Teil4  DIE STANDARDKLASSEN VON EIFFEL
       =====================================
       
       In diesem Teil werden
       - einige der Basis-Klassen
       - der Sortierer 
       - einige der Container-Klassen und
       - einige der Graphen-Klassen
       von Eiffel/S beschrieben.
       Die Erluterungen orientieren an der Version 1.2 des Compilerhandbu-
       ches.

       Prozeduren wie
                       clone , copy , equal , is_equal 
       sind fr jedes Eiffel-Programm automatisch verfgbar.
       Diese features gehren zur Klasse  ANY  und die ist universell in dem 
       Sinne, da jede Klasse automatisch von  ANY  erbt, ohne da dies ex-
       plizit in der inherit-Klausel erwhnt werden mte.
       [ Genauer gesagt ist es so, da die genannten features sich nicht in  
       ANY , sondern in  GENERAL  befinden und da  ANY  (ber PLATFORM ) von
       GENERAL  erbt. ]
       Die Klasse  ANY  enthlt lediglich Routinen und keinerlei Attribute.
       ANY  ist der richtige Ort, um solche Routinen dort hineinzuschreiben,
       die von allen Klassen eines greren Projekts gebraucht werden.

       Beispielsweise sind Input-Output-features, Formatmanipulationen und
       Dateizugriff fr fast alle interaktiven Programme wichtig und knnten
       in  ANY  kreiert werden:

       io : BASIC_IO is
           once
               !!result
           end
       --------------------
       fmt : FORMAT is
           once
               !!result
           end
       ---------------------
       fs : FILE_SYSTEM is
           once
               !!result.make
           end

       Zur Erluterung:
       In der obersten Prozedur wird ein Objekt vom Typ  BASIC_IO  kreiert,
       so da dessen features fr Input-Output Zwecke bereitstehen.
       In                    io : BASIC_IO is once ...
       wird das Sprachelement  once  benutzt, denn es vllig ausreichend, die
       Input-Output-features ber die Laufzeit des Projektes ein fr allemal
       bereitzustellen. 
       Eine once-Funktion wird ber die Laufzeit des Programms nur das aller-
       erste mal ausgewertet und verhlt sich danach wie eine Konstante.

       Die Autoren der interaktiven Eiffel/S Beispielprogramme sind aller-
       dings anders vorgegangen. Sie haben  ANY  gelassen, wie es ist und
       stattdessen die obigen Kreierungen in eine eigene Klasse  INTERAKTIV
       hineingeschrieben, die von allen Beispielprogrammen geerbt wird.
       Dadurch ist dafr gesorgt, da die Programme auf allen Eiffel/S Plat-
       formen lauffhig bleiben.
       
       Man kann zwar Prozeduren in  ANY  hineinschreiben, aber mglichst kein
       einziges Attribut! Weil nmlich  ANY  Vorgnger von jeder Klasse ist,
       wrde dies Attribut bei Laufzeit in JEDEM Objekt Platz beanspruchen.

       
       Die Klasse  COMPARABLE  enthlt die Operatoren
                               < , > , <= , >=
       Der Operator  <  ist aufgeschoben. Die drei anderen Operatoren sind 
       durch  <  ausgedrckt und sind effektiv.
       Eine Klasse, die Ordnungsstruktur braucht und daher von  COMPARABLE  
       erbt, braucht nur  <  neu zu definieren und bekommt die anderen 
       features von  COMPARABLE  "umsonst".


       Die Klasse  NUMERIC  enthlt die arithmetischen Operatoren
                           + , - , * , / , ^ (hoch)
       von denen die meisten aufgeschoben sind.
       Erst Nachfolgerklassen (wie etwa  INTEGER ) effektivieren diese 
       features.

       Die Klassen  COMPARABLE  und  NUMERIC  sind spezielle Klassen, da sie
       einige Typen als konform akzeptieren, die das in der Theorie gar nicht
       sind.
       Man betrachte beispielsweise die Containerklasse

                          SORTED_LIST [G->COMPARABLE]          
       
       Normalerweise drfte man nur zu  COMPARABLE  konforme Typen als aktu-
       elle Parameter fr  G  einsetzen. Der Compiler akzeptiert aber z.B.
       auch                SORTED_LIST [INTEGER]
       obgleich in der Eiffel-Theorie der (einfache) Typ  INTEGER  zu keinem 
       anderen Typ konform ist.
       
       
       Die Klasse  INTEGER  effektiviert  die Operatoren von  COMPARABLE  und
       NUMERIC  fr die natrlichen Zahlen. 
       Die Division  a//b  ist in  INTEGER  ganzzahlig erklrt.
       Der Ausdruck  a\\b  liefert den Rest nach Division  a//b .


       Die Klasse  REAL  effektiviert die Operatoren von  COMPARABLE  und
       NUMERIC  und fgt keine neuen features hinzu.
       Der Compiler behandelt den Typ  INTEGER , als wre er zu  REAL  kon-
       form. Die Folge ist, da man Integer- und Real-Gren mischen darf.
       Ist also  r  vom Typ  REAL  und  n  vom Typ  INTEGER , so sind
                             r + n    ,     n + r
       beide legal.
       Vor Auswertung der Summe wird die Integer-Gre in Real umgewandelt.
       Es ist legitim,  1  statt  1.0  zu schreiben.


       In der generischen Klasse  ARRAY [G]  ist der gelufige Felder-Typ im-
       plementiert. Die Klasse enthlt die ntigen features, um die Daten des
       Arrays abzufragen und die Eintrge zu manipulieren.
       Die Attribute
                       size , lower , upper  (alle Typ  INTEGER )
       reprsentieren Lnge, unteren Index und oberen Index.
       Die Funktion         
                           item (index: INTEGER): G
       liefert das Objekt an der Position  index .
       Die Prozedur       
                        put (element: G , index: INTEGER)
       ersetzt das Objekt an der Position  index  durch das Objekt  element .
       Eine Variante ist die Prozedur
                      insert (element: G , index: INTEGER)
       Sie plaziert das Objekt  element  hinter die Position  index  und 
       schiebt den Rest nach "rechts".
       Ganz hnlich funktioniert
                            remove (index: INTEGER)
       Diese Prozedur entfernt das Objekt an der Position  index  und ver-
       schiebt den Rest nach "links".

       Um eine Array-Gre zu kreieren, ist es offenbar ntig, die Indexgren-
       zen mitzuteilen. Dazu enthlt die Klasse  ARRAY  eigens eine Prozedur
       zum Kreieren
                         make (lower, upper: INTEGER)
       Bei Kreierung knnen also Unter- und Obergrenze angegeben werden.
       Make  ist der eiffel-bliche Name fr Kreierungsprozeduren.
       Als Synonym enthlt die Klasse  ARRAY  die Prozedur
                                 resize
       mit genau der gleichen Semantik wie  make .

       
       Das folgende Beispiel stammt aus dem Compiler-Handbuch:

       io:  BASIC_IO
       i :  INTEGER
       a :  ARRAY [STRING]
       
       from
          !!io
          !!a.resize (...)
          i := 1
          io.get_string
       until
          io.end_of_input
       loop
          if  i > a.size  then
             a.resize (...)
          end
          a.put (io.last_string, i)
          io.get_string
          i := i+1
       end

       Dieses Fragment plaziert ein String-Objekt an jede Position des 
       Arrays. 
       Die Abbruch-Bedingung  end_of_input  tritt (laut Compiler_Handbuch)
       ein bei Bettigen von  STRG-C  (Bei den Maschinen des MN-Labors rea-
       giert  end_of_input  auf  STRG-   (?) ).

       Das obige Programmfragment ist Kunde der Klasse  BASIC_IO  und benutzt
       deren Features  get_string  und  last_string .
       BASIC_IO  hat weitere features wie  get_int, put_real ,...  fr Einga-
       be und Ausgabe in Standardformaten.
       
       Mchte man individuell formatierte Ausgabe, so kann man die Typumwand-
       lungsfunktionen der Klasse  FORMAT  benutzen.
       Eine dieser Funktionen ist
              i2s (fmt: STRING, i: INTEGER): STRING    (Integer to String)
       die ganze Zahlen in Strings umwandelt.
       
       Das Fragment              
                       io :  BASIC_IO
                       fmt : FORMAT
                       !!io
                       !!fmt
                       io.put_string (fmt.i2s ("5", 14))    
       
       liefert als Ausdruck  "   14"  , also drei Blanks und dann die  14 .


       Die Klasse  SORTER [G->COMPARABLE]  enthlt als einziges feature den
       Sortieralgorithmus.
       Die Beschrnkung  G->COMPARABLE  fr den generischen Parameter  G  ist
       ntig, da natrlich nur vollstndig geordnete Mengen sortiert werden
       knnen.
       Der Sortieralgorithmus ist als heapsort implementiert und sortiert
       die Elemente eines Arrays:
                   sort (a: ARRAY [G] , lower, upper: INTEGER)
       Lower und upper definieren den Teil des Arrays, der sortiert werden
       soll.
       
       Die gewnschte vollstndige Ordnung kann der Benutzer folgendermaen
       selbst eingeben:
       Ist  T  der aktuelle Typ der zu sortierenden Elemente, so lt man  T
       von  COMPARABLE  erben und effektiviert den in  COMPARABLE  aufgescho-
       benen Operator  "<"  . 
       Anschlieend gengt es, die zu sortierenden Elemente in das Array 
                                  a: ARRAY [T]
       einzuladen und          sort (a,lower, upper)  
       aufzurufen.
       [ Dafr, da die vom Benutzer eingegebene vollstndige Ordnung wirk-
       lich eine ist, hat der Benutzer selbst zu sorgen. Der Compiler enthlt 
       natrlich nicht die ntige "Mathematik", um derartiges zu testen. 
       "Stimmt" die Ordnung nicht, so gibt es Laufzeitfehler. ]

       Die Klasse  SORTER  entspricht eigentlich nicht der Philosophie
       objektorientierten Programmierens, wie die Autoren des Compiler-Hand-
       buches selbst betonen.
       SORTER  implementiert einen Algorithmus. Klassen sollten im Idealfall
       einen Datentyp abstrahieren und features als Serviceleistungen fr die
       Objekte dieses Datentyps bereitstellen.
       In diesem Falle ist es so, da der Algorithmus als "Expertenwissen"
       im Vordergrund steht. Dieses Wissen ist in der eigens dafr geschaffe-
       nen Klasse  SORTER  implementiert und dem Kunden damit verfgbar ge-
       macht.
       [ Im Falle von Graphenklassen haben die Autoren des Compilers die
       "richtigere" Vorgehensweise gewhlt:  Die Klasse  WT_GRAPH  abstra-
       hiert den Datentyp "Gewichteter Graph" und enthlt die wichtigsten
       graphentheoretischen Algorithmen als Serviceleistung. ]


       ---------------------------------------------------------------------


       Die Klassen des Container-Clusters von Eiffel/S dienen zur Aufbewah-
       rung von Objekten. Die Containerklassen sind einfach zu bedienen und
       sind vllig ausreichend in Fllen, wo es nur darum geht, Objekte ein-
       zulagern, um sie zu einem spteren Zeitpunkt wiederzufinden.

       Containerklassen erscheinen in vierfacher Gestalt.
       Je nachdem, ob man auf die Elemente dierekt oder mittels eines Schls-
       sels zugreift, reprsentiert der Container eine Liste (COLLECTION)
       oder eine Tafel (TABLE).
       Fr jeden Container gibt es, je nachdem, ob zwischen den Objekten eine 
       innere Ordnung besteht oder nicht, eine sortierte und eine unsortierte 
       Version.

       Die Skizze zeigt die in Eiffel/S fr das Container-Cluster gewhlte
       Vererbungsstruktur. Effektive Klassen sind nur die untersten vier.

                                 TRAVERSABLE
                                      |                       
                /                     |                        \
         COLLECTION                   |                         TABLE
             |                     TWO_WAY_                       |          
             |                   TRAVERSABLE                      |
             |            \   /                \     /            |
             |           SORTED_             SORTED_TABLE         |
             |         COLLECTION                 |               |
             |              |                     |               |
           LIST        SORTED_LIST           SORTED_TABLE    SIMPLE_TABLE



       Es gibt demnach

                 Listen, implementiert in  LIST [G]
       sortierte Listen, implementiert in  SORTED_LIST [G -> COMPARABLE]
                 Tafeln, implementiert in  SIMPLE_TABLE [K,G]
       sortierte Tafeln, implementiert in  SORTED_TABLE [K -> COMPARABLE, G]

       Der generische Parameter  G  steht fr den Typ der eingelagerten Ob-
       jekte, K  fr den Schlssel.

       Alle Container bieten Dienstleistungen an: Zum Suchen nach Objekten;
       dazu, Objekte in den Container einzulagern und Objekte aus dem Contai-
       ner zu entfernen. Container geben Auskunft ber die Anzahl der einge-
       lagerten Elemente und darber, ob sie leer oder nichtleer sind.
       Alle Containerklassen enthalten also features wie

       count: INTEGER               
       empty: BOOLEAN               
       found: BOOLEAN               (ob das gesuchte element gefunden wurde)
       found_item: G
       item (it: ITERATOR): G       (das Objekt, wo der Iterator  it  gerade 
                                     steht)

       Andere features von Container-Klassen haben unterschiedliche Signatur
       je nach Container-Typ:

                  Listentyp:                   Tafeltyp:
                 
                 add (x: G)                 add (t: G, k: K)
                 remove (x: G)              remove (k: K)
                 search (x: G)              search (k: K)
                 -------                    key (it: ITERATOR): K

       search(k)  beispielsweise bedeutet: suche nach dem Element mit Schls-
       sel  k .
       War die Suche erfolgreich, so erhlt  found  den Wert  true , und
       found_item  liefert das gefundene Objekt.

       [ Man wundert sich vielleicht, da  found_item  auch beim Listentyp
       abfragbar ist. Wenn man beim Listentyp mit  search (x: G)  ein Element
       x  sucht, so ist (denkt man) bei erfolgreicher Suche doch klar, was
       found_item  ist, nmlich  x .
       Hier kommt eine Subtilitt des im Container-Cluster vorwiegend benutz-
       ten Gleichheitsbegriffs  
                                  is_equal  
       ins Spiel.
       Das mit  search (x: G)  gefundene Objekt mu nicht unbedingt  x  sein,
       es knnte auch ein anderes Objekt sein, das dann allerdings mit  x
       Feld fr Feld bereinstimmt. ]
       
       In Eiffel/S  sind die Containerklassen als dynamische Arrays implemen-
       tiert, die je nach Bedarf wachsen oder schrumpfen. Container sind also
       nie "voll" und haben konsequenterweise kein Attribut  "full" .

       
       Fr die Art und Weise, einen Container zu durchlaufen, gibt es unter-
       schiedliche Konzepte.
       Bertrand Meyer schlgt vor, jedem Container einen Cursor zuzuordnen,
       der jederzeit einen definierten Zustand hat.
       
       Die Autoren des Eiffel/S-Compilers benutzen stattdessen "Iteratoren".
       Ein Iterator ist eine Datenstruktur, mit deren Hilfe es mglich ist,
       die Objekte des zugehrigen Containers der Reihe nach zu besuchen.
       Jede der vier oben genannten Containerklassen verfgt ber ein oder
       mehrere features der Art
                           iterator: ITERATOR  is ...      

       Die Datenstruktur  iterator  ist eine Funktion und liefert bei Lauf-
       zeit ein Objekt, das auf dem Container sozusagen "sitzt" und das sei-
       nerseits mit den features 
                      first   --  gehe zum ersten Objekt des Containers
                      forth   --  gehe zum nchsten Objekt des Containers
                      stop    --  breche den Durchlauf durch den Container ab
                      finished: BOOLEAN
       ausgestattet ist, mit denen es sich auf "seinem" Container bewegen
       kann.
       
       Sortierte Container haben leistungsfhigere Iteratoren vom Typ
       TWOWAY_ITER , die mit zwei weitere features
                                   back 
                                   last
       ausgestattet sind, um sich auch in "Gegenrichtung" durch den Container
       bewegen zu knnen

       Es ist durchaus legitim, fr einunddasselbe Containerobjekt mehr als
       einen Iterator aktiv zu halten.
       Da diese Mglichkeit wichtig sein kann, zeigt sich in den Klassen des
       Graphenclusters. Einundderselbe Graph kann einen Iterator fr "Suche
       in die Breite" haben, einen zweiten fr "Suche in die Tiefe" und einen
       dritten (falls der Graph azyklisch ist) fr "Topologische Suche".
       
       Iteratoren sind als Funktion implementiert, wie das Fragment aus der
       Klasse  TRAVERSABLE  zeigt:

       iterator : ITERATOR is
             -- Returns an iterator object which is prepared to
             -- traverse the current container. The `first' routine
             -- of the iterator has already been called and does not
             -- need to be called explicitly unless one wants to
             -- restart the traversal.
           do
               !!result.make (current)
               init_first (result)
           end

       Also: Bei Zugriff auf das feature  iterator  wird ein Iteratorobjekt
       kreiert und automatisch auf das erste Containerelement gesetzt.
       
       [ Die hier gewhlte Implementierung des Iterators ist ein Beispiel fr
       eine "gekapselte Kreierung".
       Wenn eine Klasse hufig Objekte eines gewissen Typs bentigt (wie ein
       Container hufig einen Iterator bentigt), so kapsele man die Kreie-
       rung in einer Kreierungsfunktion, die man dann nur aufzurufen braucht.
       Damit ist man der Mhe enthoben, stndig "per Hand" zu kreieren.
       (Handbuch, S.502/03). ]
       

       Wie Iteratoren zu benutzen sind, demonstrieren die beiden folgenden
       Beispiele aus dem Compiler-Handbuch: einmal traditioneller Durchlauf
       durch ein Array mit Iterationsindex  i , und das andere Mal Durchlau-
       fen einer Menge mit Hilfe eines Iterators  it .


       a    : ARRAY [G]                              
       i    : INTEGER  
       stop : BOOLEAN    
       ...
       from
          i:=1
       until
          stop or else i > a.size
       loop
          if a.item (i) = ... then
             stop := true
          else
             do_something_with (a.item (i))
          i := i+1
          end
       end


       ----------------------------------------
       

       l   :  LIST [G]
       it  :  ITERATOR
       ...
       from
          it := l.iterator
       until
          it.finished
       loop
          if l.item (it) = ... then
             it.stop
          else
             do_something_with (l.item (it))
             it.forth
          end
       end
            

       Zur Erluterung: 
       Die explizite Positionierung des Iterators auf das erste Objekt des
       Containers mittels  it.first  ist unntig, da der Aufruf  l.iterator
       dies wie schon erwhnt automatisch erledigt.
       Das Attribut  finished: BOOLEAN  ist ein weiteres feature der Klasse
       ITERATOR  mit Wert  true , wenn alle Elemente des Containers schon
       durchlaufen wurden oder wenn der Iterator gestoppt wurde.


       ------------------------------------------------------------------


       Neben den bisher erluterten Containerarten (Mengen und Tafeln) sind 
       in Eiffel/S weitere spezielle, hufig benutzte Containerarten imple-
       mentiert: Stacks und Schlangen.
       Weder Stacks noch Schlangen verfgen ber Iteratoren.

       Stacks sind implementiert in der Klasse
                                  STACK [G]
       und arbeiten nach dem Prinzip "Last in - first out".
       Die Prozedur  remove  bentigt kein Argument, sie entfernt das Objekt,
       das als letztes mittels  add  auf den Stack abgelegt wurde.
       Die Funktion  item  liefert das oben auf dem Stack liegende Objekt.

       Bei Schlangen reprsentiert die Funktion  item  das "vorderste" Ele-
       ment. Die Prozedur  remove  entfernt dieses Element.
       Was das vorderste Element der Schlange ist und wie berhaupt die in-
       terne Reihenfolge der Schlange organisiert ist, hngt von der gewhl-
       ten Art der Schlange ab. In Eiffel/S sind drei Mglichkeiten imple-
       mentiert:
       
       "Normale" Schlangen sind implementiert in der Klasse
                                  QUEUE [G]
       und arbeiten nach dem Prinzip "First in - first out".
       Die Prozedur  add (x:G)  setzt ein Element ans Ende der Schlange.
       Das vorderste Element  item  der Schlange ist das am lngsten in ihr 
       befindliche Element. 

       Priorittsschlangen organisieren ihre interne Reihenfolge selber durch
       Priorittsregeln. Hier gibt es wieder zwei Mglichkeiten, je nachdem,
       ob die Elemente der Schlange eine innere Ordung besitzen oder ber 
       Schlssel geordnet sind.
       Der erste Fall ist implementiert in der Klasse
                        PRIORITY_QUEUE [G -> COMPARABLE] .
       Der zweite Fall ist implementiert in
                      KEY_PRIORITY_QUEUE [K -> COMPARABLE, G] .
       Das vorderste Element der Schlange ist jeweils das erste Element be-
       zglich der gewhlten Ordnung.
       Die Prozedur  add (x: G)  bzw.  add (k: K, x: G)  positioniert ein 
       Element an die der Ordnung entsprechende Stelle.

       
       Neben den bisher beschriebenen sind in Eiffel/S einige weitere Con-
       tainerklassen implementiert.
       Es gibt die Klasse
                                  HASH_TABLE ,
       die fr groe Container adquat ist und bei der die eingelagerten Ob-
       jekte eine Unteraufteilung in 'slots' haben.
       Es gibt die Klasse(n)
                                   CATALOG ,
       die "Mengen von Mengen" implementieren und die in Teil 3 schon erlu-
       tert wurden.
       Ein Spezialfall von  CATALOG  ist
                                  DICTIONARY ,
       wo an der Position des Iterators keine Liste (Typ COLLECTION[G] ),
       sondern doch nur ein Element (Typ  G ) eingelagert ist.

       
       --------------------------------------------------------------------


       Im Graph-Cluster von Eiffel/S sind ungwichtete und gewichtete Graphen-
       strukturen und zwar jeweils fr den ungerichteten und gerichteten Fall
       implementiert.
       Allen gemeinsam ist ein generischer Parameter  G , der fr die "Infor-
       mation" (die Daten) steht, die von ECKEN des Graphen getragen wird.
       Mit "gewichteten" Graphen sind in Eiffel/S solche Graphen gemeint, die
       auch (reellwertige) Daten auf den KANTEN tragen.
       In Eiffel/S sind alle Graphen effizient ber Kantenlisten (und nicht
       etwa mittels Adjazenzmatrizen) implementiert.

       Implementiert man Graphen durch Kantenlisten, so ist der Grundbaustein
       die Ecke, die gekennzeichnet ist durch die Liste der mit ihr inziden-
       ten Kanten
                   edges : ARRAY [EDGE [G]]   (nicht ffentlich)
       Dazu kommt das Attribut
                                  item: G
       fr die Daten, die der Ecke zugeordnet sind.
  

       class VERTEX [G]
       inherit
           TRAVERSABLE
               redefine
                   iterator, inside, first, next
           end
       
       creation
           make

       feature
           item   : G
           n_edge : INTEGER
           ...
       -----------------------------------------------------------

           make (x : G) is
               -- Make `x' the item attached to this vertex.
               do
                   item   := x
               end
       -----------------------------------------------------------
           ...
           add_edge (v : like current) is ...
               -- Create an edge leading from `current' to `v'.

           remove_edge (v : like current) is ...

           edge_iter, iterator : EDGE_ITER [G] is ...
               -- Provide an iterator to traverse the edges
               -- leaving `current'. 
           ...
           edges  : ARRAY [EDGE [G]] 
       
       end -- class VERTEX


       Die Klasse  EDGE  enthlt als einziges Attribut die Endecke der Kante,
       so da man sich fragen kann, ob es berhaupt ntig ist, die Klasse
       EDGE  einzufhren oder ob man nicht stattdessen die Liste der inziden-
       ten Kanten ersetzen knnte durch eine Liste der adjazenten Ecken.
       Dieser Lsung htte jedoch den Nachteil, da man keine parallelen Kan-
       ten darstellen knnte. Auerdem ist die Klasse  EDGE  sehr praktisch,
       wenn man gewichtete Kanten darstellen mchte: Man erbt von  EDGE  und
       fgt das Kantengewicht als neues Attribut hinzu.
       

       class   EDGE [G]

       creation
           make

       feature
           vertex : VERTEX [G] -- 'target' end of edge
       -----------------------------------------------------------

           make (v : like vertex) is
               do
                   vertex := v
               end
       -----------------------------------------------------------
       
       end -- class EDGE


       Damit sind die fr Graphen ntigen Bausteine bereitgestellt.
       Graphen selbst werden nun implementiert als Arrays 
               verts  : ARRAY [like found_vertex]  (nicht ffentlich)
       ihrer Ecken. Im brigen ist die Graphenklasse mit allen features aus-
       gestattet, die ntig sind, um die Graphenparameter abzufragen und um
       Ecken und Kanten zu manipulieren.
       Insbesondere haben Graphen unterschiedliche Iteratoren, einen fr
       "Suche in die Breite" von einer gegebenen Wurzel aus, einen anderen
       fr "Suche in die Tiefe" und (bei azyklischen Graphen) einen weiteren
       fr "Topologische Suche".
       Bei "Suche in die Tiefe" wird der Iterator bei Aufruf automatisch auf
       einen "tiefsten" Punkt gesetzt.
       
       Die Kreierungsprozedur hat einen Parameter, mit der man angeben kann,
       ob der Graph gerichtet oder ungerichtet sein soll.
       
       
       class   GRAPH [G]

       inherit
           TRAVERSABLE
           ...
       creation
           make

       feature { ANY }

           directed : BOOLEAN  
               -- True if and only if `current' is a directed graph.
                
           n_vert : INTEGER 
               -- The number of vertices in the graph.  
  
           n_edge : INTEGER
               -- The number of edges in the graph.
           found : BOOLEAN
               -- True if and only if the last call to find_vertex
               -- was successful.

           found_vertex : VERTEX [G]
               -- If the last call to find_vertex was successful,
               -- then found_vertex is the vertex found;
               -- otherwise it is void.
           ...
       -----------------------------------------------------------

           make (dir : BOOLEAN) is ...
               -- Establishes whether the graph is directed (`dir' = true)
               -- or undirected (`dir' = false).

   
           iterator : VERTEX_ITER [G] is ...
               -- Provides an iterator suitable for traversing all
               -- vertices of the graph. Nothing is guaranteed
               -- about the order of traversal.


           item (it : ITERATOR) : G is ...
               -- The item at the vertex on which iterator `it'
               -- is currently standing.

           vertex (it : ITERATOR) : like found_vertex is ...
               -- The vertex on which the iterator `it'
               -- is currently standing.

           find_vertex (x : G) is ...
               -- Search for a vertex with `item' = `x'.
               -- If there is one, set `found' = true and `found_vertex'
               -- to the vertex found. Otherwise set `found' = false
               -- and `found_vertex' = void.
           ...

           add_vertex (x : G) is ...
               -- If there is as yet no vertex with `item' = `x'
               --  then add one. Otherwise do nothing.

           remove_vertex (x : G) is ...
               -- If there is a vertex having `x' as item,
               -- then this vertex is removed. In addition all edges
               -- beginning or ending in this vertex are removed.
               -- If there is no such vertex the procedure is without effect.

           add_edge (src, tgt : G) is ...
               -- Create an edge in the graph from the vertex with
               -- `item' = `src' to the vertex with `item' = `tgt'.
               -- If one or both of these vertices is not yet in the
               -- graph, then it is added.

           remove_edge (src, tgt : G) is ...
               -- If there are vertices having `src' and `tgt'
               -- as their items and if there is an edge connecting
               -- them, then this edge is removed.
               -- Otherwise the procedure is without effect.
           ...

           adj_matrix : ARRAY2 [INTEGER] is ...
               -- The result is a `n_vert' x `n_vert' matrix of integers.
               -- It is the adjacency matrix representation of the graph.

           bf_iter (s : G) : BREADTH_ITER [G] is ...
               -- Provides an iterator suitable for breadth first
               -- traversal of the graph `current'. The traversal begins
               -- at the vertex with `item' = `s'.

           df_iter : DEPTH_ITER [G] is ...
               -- Provides an iterator suitable for depth first
               -- traversal of the graph.
 
           top_iter : TOP_ITER [G] is ...
               -- Provides an iterator suitable for topological
               -- traversal of the graph.
           ...

           acyclic : BOOLEAN is ...
               -- Yields `true' if and only if the graph contains
               -- no cycles, i.e. no chain of vertices
               -- v1, v2, ... , vk, v1 such that there is an edge
               -- from v1 to v2, from v2 to v3, etc.

           is_tree : BOOLEAN is ...
               -- Yields true if and only if the graph is a tree,
               -- i.e. is connected and contains no cycles.
           ...

           verts  : ARRAY [like found_vertex]

       end -- class GRAPH 


       
       Gewichtete Graphen haben reelle Zahlen als Gewichte auf den Kanten und
       bilden mit den ungewichteten Graphen eine parallele Vererbungsstruk-
       tur:

             EDGE                  VERTEX               GRAPH
               |                      |                   |
           WT_EDGE               WT_VERTEX             WT_GRAPH


       WT_EDGE  erbt von  EDGE  und fgt das Attribut 
                                 weight: Real
       fr die Kantenbewertung hinzu.
       WT_VERTEX  erbt von  VERTEX  und fgt das Attribut
                                  dist: REAL
       hinzu, das beispielsweise von Krzesten-Wege-Algorithmen genutzt wer-
       den kann, um die krzesten Entfernungen von der Wurzel zur aktuellen
       Ecke zu notieren.
       WT_GRAPH  erbt von  GRAPH  und hat eine Flle von features, in denen
       Algorithmen aus der Graphentheorie implementiert sind (Minimalgerste,
       Krzeste Wege ...).


       --------------------------------------------------------------------


       An einem in den Beispielprogrammen von Eiffel/S implementierten Pro-
       blem aus der Netzplantechnik, soll der Umgang mit den Graphenklassen
       illustriert werden.
       
       Angenommen ein greres Projekt ist zu bearbeiten, das aus mehreren
       Teilvorgngen mit bekannten Vorgangsdauern besteht.
       Zwischen den Vorgngen bestehen Abhngigkeiten: Von jedem Vorgang  V
       wei man, welche sonstigen Vorgnge abgeschlossen sein mssen, damit
       V  berhaupt starten kann.
       Gegeben ist nun eine feste Projektzeit und gesucht ist fr jeden Vor-
       gang der Zeitpunkt, zu dem seine Bearbeitung sptestens starten mu,
       wenn die Projektzeit eingehalten werden soll.

       Dies Problem lt sich offensichtlich durch einen Graphen modellieren.
       Die Vorgnge entsprechen den Ecken und die Abhngigkeiten den Kanten.
       Bei den Abhngigkeiten kommt es auf die Reihenfolge der beiden betei-
       ligten Vorgnge an, also ist der Graph gerichtet.
       Die Vorgangsdauern sind den Ecken des Graphen zugeordnet, man braucht
       keine Gewichte auf den Kanten. Es reicht daher, mit der "ungewichte-
       ten" Graphenklasse  GRAPH [G]  zu arbeiten.

       Zur Codierung der Information, die einem Vorgang (einer Ecke) zugeord-
       net ist, dient die Klasse  ACTIVITY :

       
       class   ACTIVITY
       ...
       feature
           descript : STRING
           duration : INTEGER
           earliest : INTEGER
           latest   : INTEGER
       ...
       set_latest (t : like latest) is
           do
               latest := t
           end
       end -- class ACTIVITY
  
       
       Hier ist
       -  descript  der Name des Vorgangs
       -  duration  die (gegebene) Vorgangsdauer und
       -  latest  die (zu berechnende) sptest mgliche Anfangszeit.

       Die gesuchten Sptesten Anfangszeiten knnen nun mit der folgenden 
       Prozedur berechnet werden, wobei ausgiebig von den Iteratoren Gebrauch
       gemacht wird, die fr Graphen verfgbar sind.


       plan_graph : GRAPH [ACTIVITY]
       ...
    
       make_plan (done_by : INTEGER) is
           local
               it  : ITERATOR
               a   : ACTIVITY
               v   : VERTEX [ACTIVITY]
               ts  : STACK [VERTEX [ACTIVITY]]
               dfi : ITERATOR
           do
               from
                   it := plan_graph.iterator
               until
                   it.finished
               loop
                   a := plan_graph.item (it)
                   a.set_earliest (0) 
                   a.set_latest (done_by)
                   it.forth
               end

               from
                   !!ts.make
                   dfi := plan_graph.df_iter
               until
                   dfi.finished
               loop
                   v := plan_graph.vertex (dfi)
                   calculate_latest (v)
                   ts.add (v)
                   dfi.forth
               end
               ...
           end
       -------------------------------------------
    
       calculate_latest (v : VERTEX [ACTIVITY]) is
                   -- assumes successors of v are finished 
           local 
               min : INTEGER
               dur : INTEGER
               ei  : EDGE_ITER [ACTIVITY]
           do
               from
                   dur := v.item.duration
                   min := v.item.latest
                   ei  := v.edge_iter
               until
                   ei.finished
               loop
                   if min > ei.item.latest - dur then
                       min := ei.item.latest - dur
                   end
                   ei.forth
               end
               v.item.set_latest (min)
           end

       
       Zur Erluterung:
       done_by  ist die vorgegebene Projektdauer.
       In der ersten Schleife der Prozedur  make_plan  werden die Sptesten
       Zeitpunkte  latest  aller Ecken auf die Projektdauer hochgesetzt.
       [ Ferner werden die hier nicht weiter diskutierten "Frhesten Zeit-
       punkte" auf  0  gesetzt. ]
       Anschlieend wird der Iterator  df_iter  fr "Suche in die Tiefe" auf-
       gerufen. Dieser Iterator startet bei der letzten Ecke des azyklischen
       Graphen und hat die Eigenschaft, eine Ecke erst dann zu besuchen, wenn
       er alle Nachfolger dieser Ecke bereits besucht hat.
       Ist  v  die  aktuelle Position von  dfi = df_iter , so wird mit
                             calculate_latest (v)
       der Spteste Zeitpunkt von  v  berechnet  [ und im brigen wird fr
       andere Zwecke diese Ecke  v  noch auf einen Stack abgelegt] .
       
       Die Berechnung des Sptesten Zeitpunktes  latest  der Ecke  v  ge-
       schieht folgendermaen:
       Mit                      ei  := v.edge_iter
       wird der Iterator aufgerufen, der die  Kanten abluft, welche mit der
       Ecke  v  inzidieren.
       Der Zugriff               ei.item.latest
       greift zu auf den Sptesten Zeitpunkt der Endecke der aktuellen Kante
       (der ja bereits berechnet worden ist).
       Wenn  dur  die Vorgangsdauer der Ecke  v  ist, so mu der Vorgang (die 
       Ecke)  v  sptestens zum Zeitpunkt
                               ei.item.latest - dur
       beginnen, wenn die Projektdauer  done_by  eingehalten werden soll.
       Das Minimum dieser  ei.item.latest - dur  ber die Nachfolger der Ecke
       v  gerechnet ist der Spteste Zeitpunkt  min  von  v  und wird mit
                            v.item.set_latest (min)
       an das Attribut  latest  der Ecke  v  bergeben.
