Contributor: N PAUL RIVERS

{
From: nrivers@silver.ucs.indiana.edu (n paul rivers)

   I did manage to find part of the code that was once used to write
a preliminary version of a Huffman compression program.  Oddly, some of
the procedures were missing, and worse, there were no comments.  I
apologize for all this, but hopefully it will be some use in spite of
the inadequacies.  Also, your post makes mention of wanting the "optimum"
way to do this -- well, this isn't it!  But it will work, and perhaps it
will give you some ideas.
}

Type
  TNodePtr = ^TNode;
  TNode = Record
    Count : Longint;
    Parent, Left, Right : TNodePtr;
    end;
  TNodePtrArray = Array[0..255] of TNodePtr;
  TFreqArray = Array[0..255] of Longint;
  TFileName = String[12];
  TBitTable = Array[0..255] of Byte;

Var
  Source, Dest : TFileName;
  LeafNodes : TNodePtrArray;
  Freq : TFreqArray;
  BitTable : TBitTable;
  TotalBytes : Longint;
  P : Pointer;
  C : Char;

Procedure GetFileNames(var Source, Dest : TFileName);
  Begin
    If ParamCount<>2 then begin
       writeln('Specify the file to compress & its destination name.');
       writeln; halt; end;
    Source := ParamStr(1);
    Dest := ParamStr(2);
  End;

Procedure InitializeArrays(var Leaf : TNodePtrArray; 
          var Freq : TFreqArray; var BitTable : TBitTable);
  Var
    B : Byte;
  Begin
    For B := 0 to 255 do begin
      Leaf[B] := nil;
      Freq[B] := 0;
      BitTable[B] := '';
    End;
  End;

Procedure GetByteInfo(Source : TFileName; var Freq : TFreqArray; 
                      var TotalBytes : Longint);
  Var
    S : File of Byte;
    inputByte : Byte;
  Begin
    Assign(S, Source);
    Reset(S);
    TotalBytes := 0;
    While not(eof(s)) do begin
      read(s,inputByte);
      Inc(Freq[inputByte]);
      Inc(TotalBytes);
    end;
    Close(S);
  End;

Procedure LoadNodeArray(var LeafNodes : TNodePtrArray; 
                        var Freq : TFreqArray);
  Var
    B : Byte;
    Node : TNodePtr;
  Begin
    Node := Nil;
    For B := 0 to 255 do if Freq[B]>0 then begin
      New(Node);
      Node^.Parent := nil;
      Node^.Left := nil;
      Node^.Right := nil;
      Node^.Count := Freq[B];
      LeafNodes[B] := Node;
      Node := Nil;
    End;
  End;

Procedure GetMinInFreeArray(var min1, min2 : byte; var CFA : TNodePtrArray);
  Var b : byte;
      minCount1, minCount2 : Longint;
  Begin
    minCount1 := 1000000000; minCount2 := minCount1;
    min1 := 0; min2 := 0;
    for b := 0 to 255 do if CFA[b]<>nil then begin
      if minCount1>CFA[b]^.Count then begin
         min2 := min1; min1 := b;
         minCount2 := minCount1; minCount1 := CFA[b]^.Count;
         end
      else if ((minCount2>=CFA[b]^.Count) and (b<>min1)) then begin
         minCount2 := CFA[b]^.Count; min2 := b;
         end;
    end;
  End;


Procedure BuildTree(var LeafNodes : TNodePtrArray);
  Var
     CFA, NFA : TNodePtrArray;  Node : TNodePtr;
     {CFA = current free array,  NFA = next free array
      once two nodes in the current free array have been combined to
      form one node at one level 'up' the tree, then this new node must
      be placed in the NFA for the upcoming round of combining nodes}
     FreeThisLvl, NoCombs : Word;
     {FreeThisLvl = continue combining nodes at each level until after one
      round of combining, there is only one node left.  "there can be only
      one!"  NoCombs = number of combinations to be made at the given level"}
     Cnt, min1, min2 : Byte;
  Begin
     FreeThisLvl := 0; Node := nil;
     for cnt := 0 to 255 do begin
         NFA[cnt] := nil;
         CFA[cnt] := LeafNodes[cnt];
         if CFA[cnt]<>nil then Inc(FreeThisLvl);
     end;

     While FreeThisLvl>1 do begin
       NoCombs := (FreeThisLvl div 2);
       For cnt := 1 to NoCombs do begin
           GetMinInFreeArray(min1,min2,CFA);
           New(Node);
           Node^.Parent := nil;
           Node^.Right := CFA[min1]; Node^.Left := CFA[min2];
           Node^.Count := CFA[min1]^.Count + CFA[min2]^.Count;
           Node^.Left^.Parent := Node;
           Node^.Right^.Parent := Node;
           NFA[cnt] := Node; Node := Nil;
           CFA[min1] := nil; CFA[min2] := nil;
       end;

       For cnt := 0 to 255 do if CFA[cnt]<>nil then NFA[0] := CFA[cnt];

       For cnt := 0 to 255 do begin
         CFA[cnt] := NFA[cnt];
         NFA[cnt] := nil;
       end;

       FreeThisLvl := 0;
       For cnt := 0 to 255 do if CFA[cnt]<>nil then Inc(FreeThisLvl);

     end;
  End;

Procedure BuildBitTable(var LeafNodes : TNodePtrArray; 
                        var BitTable : TBitTable)
  Begin
    {
    To build the bit table for a given value, set, e.g. ptr1 and ptr2, to
    point to the given leafnode.  then set ptr1 to point at the parent.
    then if ptr1^.left = ptr2 then the first bit for the given node is 0,
    else it is 1.  continue this process until you reach the top of the 
    tree.
    }
  End;

Procedure CompressFile(Source, Dest : TFileName; var BitTable : TBitTable; 
                       TotalBytes : Longint);
  Begin
    {
    remember to write the necessary tree information for decompression in
    the compressed file.  also, since the last byte of the file might 
    contain bits not relevant to decoding, i've decided to just keep track
    of the total # of bytes in the original file.  so don't forget to
    write this number to the file as well.
    }
  End;

BEGIN

  GetFileNames(Source,Dest);
  InitializeArrays(LeafNodes,Freq,BitTable);
  writeln('Gathering info...'); writeln;
  GetByteInfo(Source,Freq,TotalBytes);
  Mark(P);
  LoadNodeArray(LeafNodes,Freq);
  BuildTree(LeafNodes);
  BuildBitTable(LeafNodes,BitTable);
  Release(P);
  writeln('Compressing file...'); writeln;
  CompressFile(Source,Dest,BitTable,TotalBytes);
  writeln; writeln;

END.