(*-------------------------------------------------------------------------*)
(*                                                                         *)
(*  Amiga Oberon Library Module: UntracedAVL          Date: 02-Nov-92      *)
(*                                                                         *)
(*   © 1992 by Fridtjof Siebert                                            *)
(*                                                                         *)
(*-------------------------------------------------------------------------*)

MODULE UntracedAVL;

IMPORT BasicTypes;

TYPE

  NodePtr * = UNTRACED POINTER TO Node;
  Node * = RECORD (BasicTypes.ANYDesc)
    l   - : NodePtr;
    r   - : NodePtr;
    bal   : INTEGER;
  END;

  RootPtr * = UNTRACED POINTER TO Root;
  Root * = RECORD (BasicTypes.ANYDesc)
    root - : NodePtr;
    cmp    : PROCEDURE(a,b: NodePtr): INTEGER;
    find   : PROCEDURE(VAR root: Root; a: NodePtr): INTEGER;
  END;


  CompProc * = PROCEDURE(a,b: NodePtr): INTEGER;

(* Prozedur zum Vergleichen zweier Nodes.
   Ergebnis ist:
     = 0, wenn beide gleich sind
     < 0, wenn a < b
     > 0, wenn a > b
*)

  FindProc * = PROCEDURE(VAR root: Root; a: NodePtr): INTEGER;

(* Diese Prozedur wird von Find() verwendet.
   Ihr Ergebnis ist
     = 0, wenn a das gesuchte Element ist
     < 0, wenn a < dem gesuchten Element ist
     > 0, wenn a > dem gesuchten Element ist
*)

  UANY * = UNTRACED POINTER TO BasicTypes.ANYDesc;

  DoProc * = PROCEDURE(a: NodePtr);
  DoProc2 * = PROCEDURE(x,par: UANY);


CONST

(* Node.bal *)
  left  * = -1;
  ok    * = 0;
  right * = 1;


TYPE
  String * = ARRAY 80 OF CHAR;

  SNodePtr * = UNTRACED POINTER TO SNode;
  SNode * = RECORD (Node)
              name * : String;
            END;

  SRoot * = RECORD (Root)
              findstr: String;
            END;


(*-------------------------------------------------------------------------*)


(*------  Init:  ------*)


PROCEDURE Init* (VAR root: Root;
                     cmp:  CompProc;
                     find: FindProc);

(* initialisiert (leert) den AVL-Baum und setzt die Vergleichsprozedur und
   Suchprozedur. Diesen Prozeduren werden die Nodes übergeben, die mit
   Add() in den Baum eingetragen wurden. *)

BEGIN
  root.root := NIL;
  root.cmp  := cmp;
  root.find := find;
END Init;


(*------  Add:  ------*)


PROCEDURE Add* (VAR root: Root;
                    node: NodePtr): BOOLEAN;

(* fügt node in den Baum ein. Das Ergebnis ist FALSE, wenn ein gleicher
   (z.B. gleichnamiger) Knoten bereits im Baum enthalten ist, also nicht
   eingefügt werden konnte. *)

VAR
  res: BOOLEAN;
  grown: BOOLEAN;
  n1,n2: NodePtr;

  PROCEDURE Search(VAR n: NodePtr);

  VAR c: INTEGER;

  BEGIN
    IF n=NIL THEN                       (* neues Element einfügen: *)
      grown := TRUE;
      n := node;
      n.l := NIL; n.r := NIL; n.bal := 0;
      res := TRUE;
    ELSE
      c := root.cmp(n,node);
      IF c>0 THEN
        Search(n.l);
        IF grown THEN                   (* linker Zweig gewachsen: *)
          CASE n.bal OF
          right: n.bal := ok; grown := FALSE |
          ok:    n.bal := left |
          left:  n1 := n.l;             (* rebalance *)
                 IF n1.bal = left THEN  (* einfache LL Rotation *)
                   n.l := n1.r; n1.r := n; n.bal := ok; n := n1
                 ELSE                   (* doppelte LR Rotation *)
                   n2 := n1.r; n1.r := n2.l; n2.l := n1; n.l := n2.r; n2.r := n;
                   IF n2.bal=left  THEN n .bal := right ELSE n .bal := ok END;
                   IF n2.bal=right THEN n1.bal := left  ELSE n1.bal := ok END;
                   n := n2;
                 END;
                 n.bal := ok; grown := FALSE |
          END;
        END;
      ELSIF c<0 THEN
        Search(n.r);
        IF grown THEN                   (* rechter Zweig gewachsen: *)
          CASE n.bal OF
          left:  n.bal := ok; grown := FALSE |
          ok:    n.bal := right |
          right: n1 := n.r;             (* rebalance *)
                 IF n1.bal = right THEN (* einfache RR Rotation *)
                   n.r := n1.l; n1.l := n; n.bal := ok; n := n1
                 ELSE                   (* doppelte RL Rotation *)
                   n2 := n1.l; n1.l := n2.r; n2.r := n1;
                   n.r  := n2.l; n2.l := n;
                   IF n2.bal = right THEN n .bal := left  ELSE n .bal := ok END;
                   IF n2.bal = left  THEN n1.bal := right ELSE n1.bal := ok END;
                   n := n2;
                 END;
                 n.bal := ok; grown := FALSE |
          END;
        END;
      ELSE
        grown := FALSE;
        res := FALSE;
      END;
    END;
  END Search;

BEGIN
  Search(root.root);
  RETURN res;
END Add;


(*------  Find:  ------*)


PROCEDURE Find* (VAR root: Root): NodePtr;

(* Sucht denjenigen Knoten, für den Root.FindProc das Ergebnis 0 liefert und
   gibt seine Adresse oder NIL zurück. *)

VAR
  n: NodePtr;
  c: INTEGER;

BEGIN
  n := root.root;
  LOOP
    IF n=NIL THEN RETURN NIL END;
    c := root.find(root,n);
    IF    c<0 THEN n := n.r
    ELSIF c>0 THEN n := n.l
              ELSE RETURN n END;
  END;
END Find;


(*------  Remove:  ------*)


PROCEDURE Remove* (VAR root: Root;
                       node: NodePtr): BOOLEAN;

(* Entfernt node aus dem Baum. Ergebnis ist FALSE, wenn node nicht im
   Baum enthalten war. Der Speicher von node muß danach noch freigegeben werden. *)

VAR
  h: BOOLEAN;
  deleted: BOOLEAN;


  PROCEDURE BalL(VAR n: NodePtr);
  VAR n1, n2 : NodePtr;
      b1, b2 : INTEGER;
  BEGIN
    CASE n.bal OF
    left:  n.bal := ok |
    ok:    n.bal := right; h := FALSE |
    right: n1 := n.r; b1 := n1.bal;     (* rebalance *)
           IF b1 # left THEN            (* single RR rotation *)
             n.r := n1.l; n1.l := n;
             IF b1 = ok THEN n.bal := right; n1.bal := left; h := FALSE
                        ELSE n.bal := ok;    n1.bal := ok;               END;
             n := n1;
           ELSE                         (* double RL rotation *)
             n2 := n1.l; b2 := n2.bal;
             n1.l := n2.r; n2.r := n1;
             n .r := n2.l; n2.l := n;
             IF b2 = right THEN n .bal := left  ELSE n .bal := ok END;
             IF b2 = left  THEN n1.bal := right ELSE n1.bal := ok END;
             n := n2; n2.bal := ok;
           END |
    END;
  END BalL;

  PROCEDURE BalR(VAR n: NodePtr);
  VAR n1, n2 : NodePtr;
      b1, b2 : INTEGER;
  BEGIN
    CASE n.bal OF
    right: n.bal :=  ok |
    ok:    n.bal := left; h := FALSE |
    left:  n1 := n.l; b1 := n1.bal;     (* rebalance *)
           IF b1 # right THEN           (* single LL rotation *)
             n.l := n1.r; n1.r := n;
             IF b1 = ok THEN n.bal := left; n1.bal := right; h := FALSE
                        ELSE n.bal := ok;   n1.bal := ok;               END;
             n := n1;
           ELSE                         (* double LR rotation *)
             n2 := n1.r; b2 := n2.bal;
             n1.r := n2.l; n2.l := n1;
             n .l := n2.r; n2.r := n;
             IF b2 = left  THEN n .bal := right ELSE n .bal := ok END;
             IF b2 = right THEN n1.bal := left  ELSE n1.bal := ok END;
             n := n2; n2.bal := ok;
           END |
    END;
  END BalR;

  PROCEDURE Rem(VAR n: NodePtr);

  VAR
    c: INTEGER;
    New,Q: NodePtr;

    PROCEDURE del(VAR m: NodePtr);
    VAR
      s: Node;
    BEGIN
      IF m.r#NIL THEN
        del(m.r); IF h THEN BalR(m) END
      ELSE
        New := m;
        m := m.l;
        h := TRUE;
      END;
    END del;

  BEGIN
    IF n # NIL THEN
      c := root.cmp(n,node);
      IF    c>0 THEN Rem(n.l); IF h THEN BalL(n) END
      ELSIF c<0 THEN Rem(n.r); IF h THEN BalR(n) END
      ELSE
        deleted := TRUE;
        IF    n.r=NIL THEN n := n.l; h := TRUE
        ELSIF n.l=NIL THEN n := n.r; h := TRUE
        ELSE
          Q := n;
          del(n.l);
          n := New;
          n.l := Q.l;
          n.r := Q.r;
          n.bal := Q.bal;
          IF h THEN BalL(n) END
        END;
      END;
    END;
  END Rem;

BEGIN
  h := FALSE; deleted := FALSE; Rem(root.root); RETURN deleted;
END Remove;


(*------  DoForward:  ------*)


PROCEDURE DoForward* (root: Root;
                      proc: DoProc);

(* ruft proc nacheinander (von links nach rechts) mit allen Elementen des Baumes
   auf *)

  PROCEDURE DoFrwd(n: NodePtr);

  BEGIN
    IF n#NIL THEN
      DoFrwd(n.l);
      proc(n);
      DoFrwd(n.r);
    END;
  END DoFrwd;

BEGIN
  DoFrwd(root.root);
END DoForward;


(*------  DoBackward:  ------*)


PROCEDURE DoBackward* (root: Root;
                       proc: DoProc);

(* ruft proc nacheinander rückwärts (von rechts nach links) mit allen
   Elementen des Baumes auf *)

  PROCEDURE DoBkwd(n: NodePtr);

  BEGIN
    IF n#NIL THEN
      DoBkwd(n.r);
      proc(n);
      DoBkwd(n.l);
    END;
  END DoBkwd;

BEGIN
  DoBkwd(root.root);
END DoBackward;


(*------  Dispose:  ------*)


PROCEDURE Dispose* (VAR root: Root);

(* Ruft DISPOSE() mit allen Elementen des Baumes auf, gibt also seinen
   gesamten Speicher frei. *)

  PROCEDURE Disp(n: NodePtr);

  BEGIN
    IF n#NIL THEN
      Disp(n.l);
      Disp(n.r);
      DISPOSE(n);
    END;
  END Disp;

BEGIN
  Disp(root.root); root.root := NIL;
END Dispose;


(*-------------------------------------------------------------------------*)


PROCEDURE (tree: RootPtr) Add        * (x: UANY);
(*
 * add x to tree.
 *)
BEGIN
  IF ~ Add(tree^,x(Node)) THEN HALT(20) END;
END Add;


PROCEDURE (tree: RootPtr) Remove     * (x: UANY);
(* removes x from tree.
 *)
BEGIN
  IF ~ Remove(tree^,x(Node)) THEN HALT(20) END;
END Remove;


PROCEDURE (tree: RootPtr) nbElements * (): LONGINT;
(* returns the number of elements within tree.
 *)

  PROCEDURE CountElements(n: NodePtr): LONGINT;
  BEGIN
    IF n=NIL THEN RETURN 0
             ELSE RETURN CountElements(n.l) + CountElements(n.r) + 1
    END;
  END CountElements;

BEGIN
  RETURN CountElements(tree.root);
END nbElements;


PROCEDURE (tree: RootPtr) Do         * (p: DoProc2; par: UANY);
(* calls p(x,par) for every element x stored within tree.
 * par passes some additional information to p. par is not touched by Do.
 *)

  PROCEDURE Do(n: NodePtr);
  BEGIN
    IF n#NIL THEN
      Do(n.l);
      p(n,par);
      Do(n.r);
    END;
  END Do;

BEGIN
  Do(tree.root);
END Do;


(*-------------------------------------------------------------------------*)


(*------  internes:  ------*)


PROCEDURE * SCompProc(a,b: NodePtr): INTEGER;

BEGIN
  WITH a: SNode DO
    WITH b: SNode DO
      IF    a.name>b.name THEN RETURN  1
      ELSIF a.name<b.name THEN RETURN -1
                          ELSE RETURN  0 END;
    END;
  END;
END SCompProc;


PROCEDURE * SFindProc(VAR root: Root; a: NodePtr): INTEGER;

BEGIN
  WITH a: SNode DO
    WITH root: SRoot DO
      IF    a.name>root.findstr THEN RETURN  1
      ELSIF a.name<root.findstr THEN RETURN -1
                                ELSE RETURN  0 END;
    END;
  END;
END SFindProc;


(*-------------------------------------------------------------------------*)


(*------  SInit:  ------*)


PROCEDURE SInit*(VAR root: SRoot);

(* initialisiert (leert) String-AVL-Baum und setzt die Vergleichsprozeduren
   richtig. *)

BEGIN
  Init(root,SCompProc,SFindProc);
END SInit;


(*------  SAdd:  ------*)


PROCEDURE SAdd* (VAR root: SRoot;
                     node: SNodePtr): BOOLEAN;

(* fügt node in den Baum ein. Das Ergebnis ist FALSE, wenn ein gleichnamiger
   Knoten bereits im Baum enthalten ist, also nicht eingefügt werden
   konnte. *)

BEGIN
  RETURN Add(root,node);
END SAdd;


(*------  SFind:  ------*)


PROCEDURE SFind*(VAR root: SRoot;
                     str:  String): SNodePtr;

(* Sucht den Knoten mit dem Namen str und gibt seine Adresse oder NIL
   bei Mißerfolg zurück. *)

VAR n: NodePtr;

BEGIN
  root.findstr := str;
  n := Find(root);
  RETURN n(SNode);
END SFind;


(*-------------------------------------------------------------------------

  Bei String-AVL-Bäumen können die Prozeduren Remove, DoForward, DoBackward
  und Dispose von oben verwendent werden. Sie sind deshalb nicht noch
  einmal Implementiert.

-------------------------------------------------------------------------*)

END UntracedAVL.





