home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TBTREE.ZIP / SORT.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1988-07-25  |  24.0 KB  |  619 lines

  1. (* TBTree13             Copyright (c)  1988            Dean H. Farwell II    *)
  2.  
  3. unit Sort;
  4.  
  5. (****************************************************************************)
  6. (*                                                                          *)
  7. (*                       S O R T    R O U T I N E S                         *)
  8. (*                                                                          *)
  9. (****************************************************************************)
  10.  
  11.  
  12. (* This unit contains the data types and routines required to sort logical
  13.    record lists.  A logical record list can be sorted on any number of
  14.    fields.  This unit technically violates the object oriented design
  15.    techniques used throughout TBTREE because it directly manipulates an
  16.    object ( LrList) external to this unit.  This is done to optimize
  17.    performance.  To alleviate this, I could combine this unit with the
  18.    LRECLIST unit.  I opted against this since sorting is considered by
  19.    most people to be a separate type of operation than the operations
  20.    performed by the LRECLIST unit.  The decision really doesn't affect
  21.    anything as far as the user is concerned.
  22.  
  23.    Sorting using this unit is generally straighforward.  To sort a logical
  24.    record list takes two steps.  The first is to build a sort field list, a
  25.    list containing information on each of the fields to sort on.  The second
  26.    step is to call SortList with the correct parameters.  Once the sort is
  27.    completed you should call DestroySortList which will return the space
  28.    used to store the sort field list.  This last step does not need done if
  29.    you intend to sort on the same fields at a later time.
  30.  
  31.    Step 1 - Call AddFieldToSortList for each field to sort on.  In other words
  32.    if you are sorting on three fields, you must call AddFieldToSortList three
  33.    times.  When calling the routine you must supply a sort field variable (a
  34.    pointer) which is equal to NIL for the first call to the routine.  You
  35.    must also supply the byte position of the field within the logical record
  36.    and the ValueType of the field.  Again, for the first call when building a
  37.    list sPtr must be NIL.  The routine must be called once for each field.
  38.    The field passed in the first call has the highest sort precedence.  The
  39.    field passed in as the next field will have the next highest precedence etc.
  40.    The only tricky part is determining the byte position of a field within a
  41.    logical record.  Probably the only way to do it is manually determine
  42.    how much room is taken be the preceding fields.  An easy way to store it
  43.    is to have a record which contains the position for each field.  For
  44.    example:
  45.  
  46.         MyRecord = record
  47.             name     : String[20];
  48.             age      : Byte;
  49.             jobTitle : JobType;   { assume its an enumerated type }
  50.             end;
  51.  
  52.         MyRecordPos = record
  53.             namePos     : Byte;
  54.             agePos      : Byte;
  55.             jobTitlePos : Byte;
  56.             end;
  57.  
  58.          var myPos : MyRecordPos;
  59.  
  60.          procedure SetPosMyRecord;
  61.  
  62.              begin
  63.              myPos.namePos := 1;
  64.              myPos.agePos := 22;   { note its not 21 since the previous field
  65.                                      is 21 bytes (1 for the length) }
  66.              myPos.jobTitlePos := 23;
  67.              end;
  68.  
  69.          var sPtr : SortFieldList;
  70.  
  71.          procedure BuildMyList;
  72.  
  73.              begin           { sort on age then name
  74.              sPtr := NIL;    { very important !! }
  75.              AddFieldToSortList(sPtr,myPos.agePos,BYTEVALUE);
  76.              AddFieldToSortList(sPtr,myPos.namePos,STRINGVALUE):
  77.              end;
  78.  
  79.  
  80.    This method method will make it easier if you ever change you logical
  81.    record definitions.
  82.  
  83.    Step 2 - Now that you have built the list simply call SortList.  The
  84.    call is simple.  The list to sort, the file name of the data
  85.    file, the sort field list and the size of the data record are all passed
  86.    in.  The name of the data file and the logical record size is very
  87.    important.
  88.  
  89.    Once the call is made the list will be sorted and returned.  One thing
  90.    is important to realize.  Sorting takes time.  Although the calls are
  91.    simple to the user, there is a great deal of work going on behind the
  92.    scenes.  Sort times will very from seconds to hours depending on the
  93.    size of the list and to a lesser extent the size of the logical
  94.    records and the type of fields.  I have included an example program
  95.    which can be used as desired.
  96.  
  97.  
  98.    note - I used the QuickSort and BubbleSort algorithms found in
  99.           the book Data Structures and Algorithms by Aho, Hopcroft and
  100.           Ullman.  I took the liberty of making a few changes to speed
  101.           them up for this particular application.  Some of the coding
  102.           is not as straight forward as desired, but this was done for
  103.           the sake of efficiency.  By doing some tuning I managed to
  104.           decrease sorting times by as much as 30%.  This is especially
  105.           true for lists which are partially sorted (already sorted
  106.           on the first sort field).  The routines work which is
  107.           the goal anyway.                                                   *)
  108.  
  109. (* Version Information
  110.  
  111.    Version 1.1 - Not Applicable.  This unit was introduced in version 1.2
  112.  
  113.    Version 1.2 - Added this entire unit.
  114.  
  115.    Version 1.3 - No Changes                                                  *)
  116.  
  117. (*\*)
  118. (*////////////////////////// I N T E R F A C E //////////////////////////////*)
  119.  
  120. interface
  121.  
  122. uses
  123.     Compare,
  124.     FileDecs,
  125.     Files,
  126.     Logical,
  127.     LRecList,
  128.     Page;
  129.  
  130. type
  131.     SortFieldPosition = DataSizeRange;   (* position of sort field (bytes
  132.                                             within the data record           *)
  133.  
  134.     SortFieldList = ^SortField; (* used to form a linked list containing info
  135.                                    about sort fields                         *)
  136.  
  137.     SortField = record                        (* used to keep info about one
  138.                                                  sort field                  *)
  139.  
  140.               pos   : SortFieldPosition;    (* byte pos of sort field relative
  141.                                                to the beginning of the data
  142.                                                record                        *)
  143.               vType : ValueType;                        (* type of the field *)
  144.               next  : SortFieldList;
  145.               end;
  146.  
  147. (*\*)
  148. (* This routine will take a previously used list and delete all entries.  sPtr
  149.    will end up being NIL after the call.  It is important to call this routine
  150.    rather than just setting sPtr to NIL in order to return the heap space used
  151.    by the list                                                               *)
  152.  
  153. procedure DestroySortList(var sPtr : SortFieldList);
  154.  
  155.  
  156. (* This routine will add one sort field to the end of a sort field list.  The
  157.    byte position of the field within the data record and the type of the field
  158.    must be supplied.  The pointer to the sort field list must also be supplied.
  159.    The list is kept in the order of entry.  Therefore, the first entry should
  160.    be the first field to sort on, the second field should be the next field to
  161.    sort on if the first fields in two or more records contain the same value
  162.    etc.                                                                      *)
  163.  
  164. procedure AddFieldToSortList(var sPtr : SortFieldList;
  165.                              pos : SortFieldPosition;
  166.                              vType : ValueType);
  167.  
  168.  
  169. (* This routine sorts a logical record list in ascending order using the sort
  170.    field list passed in a a parameter.  The file name for the data file and the
  171.    size ot the logical records (data records) must also be passed in.  lrlst is
  172.    passed in unsorted and is returned sorted in ascending order.  If the caller
  173.    wants to traverse the list in descending order, call this routine and
  174.    traverse the list in reverse order.
  175.  
  176.    note - prior to sorting, this routine checks the heap to see if there is
  177.    sufficient heap space to perform the sort.  The routine requires 2 blocks
  178.    of heap memory each equal to the logical record size of the data records
  179.    (passed in as size).  If there is not enough memory, the list is returned
  180.    unsorted.                                                                 *)
  181.  
  182. procedure SortList(var lrLst : LrList;                       (* list to sort *)
  183.                    fName : FnString;                       (* data file name *)
  184.                    sPtr : SortFieldList;         (* list holding sort fields *)
  185.                    size : DataSizeRange);           (* size of data record
  186.                                                        (logical record)      *)
  187.  
  188. (*\*)
  189. (*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
  190.  
  191. implementation
  192.  
  193. const
  194.     BUBBLELIMIT = 7;           (* once a list has less than BUBBLELIMIT items
  195.                                    it will be sorted using a Bubble Sort    *)
  196.  
  197. (* This routine will take a previously used list and delete all entries.  sPtr
  198.    will end up being NIL after the call.  It is important to call this routine
  199.    rather than just setting sPtr to NIL in order to return the heap space used
  200.    by the list                                                               *)
  201.  
  202. procedure DestroySortList(var sPtr : SortFieldList);
  203.  
  204. var
  205.     tempPtr : SortFieldList;
  206.  
  207.     begin
  208.     while sPtr <> NIL do
  209.         begin
  210.         tempPtr := sPtr^.next;
  211.         Dispose(sPtr);
  212.         sPtr := tempPtr;
  213.         end;
  214.     end;                                   (* end of DestroySortList routine *)
  215.  
  216. (*\*)
  217. (* This routine will add one sort field to the end of a sort field list.  The
  218.    byte position of the field within the data record and the type of the field
  219.    must be supplied.  The pointer to the sort field list must also be supplied.
  220.    The list is kept in the order of entry.  Therefore, the first entry should
  221.    be the first field to sort on, the second field should be the next field to
  222.    sort on if the first fields in two or more records contain the same value
  223.    etc.                                                                      *)
  224.  
  225. procedure AddFieldToSortList(var sPtr : SortFieldList;
  226.                              pos : SortFieldPosition;
  227.                              vType : ValueType);
  228.  
  229. var
  230.     tempPtr : SortFieldList;
  231.  
  232.     begin
  233.     if sPtr = NIL then
  234.         begin
  235.         New(sPtr);
  236.         tempPtr := sPtr;
  237.         end
  238.     else
  239.         begin
  240.         tempPtr := sPtr;
  241.         while tempPtr^.next <> NIL do
  242.             begin
  243.             tempPtr := tempPtr^.next;
  244.             end;
  245.         New(tempPtr^.next);
  246.         tempPtr := tempPtr^.next;
  247.         end;
  248.     tempPtr^.pos := pos;
  249.     tempPtr^.vType := vType;
  250.     tempPtr^.next := NIL;
  251.     end;                                (* end of AddFieldToSortList routine *)
  252.  
  253.  
  254. (*\*)
  255. (* This routine sorts a logical record list in ascending order using the sort
  256.    field list passed in a a parameter.  The file name for the data file and the
  257.    size ot the logical records (data records) must also be passed in.  lrlst is
  258.    passed in unsorted and is returned sorted in ascending order.  If the caller
  259.    wants to traverse the list in descending order, call this routine and
  260.    traverse the list in reverse order.
  261.  
  262.    note - prior to sorting, this routine checks the heap to see if there is
  263.    sufficient heap space to perform the sort.  The routine requires 2 blocks
  264.    of heap memory each equal to the logical record size of the data records
  265.    (passed in as size).  If there is not enough memory, the list is returned
  266.    unsorted.                                                                 *)
  267.  
  268. procedure SortList(var lrLst : LrList;                       (* list to sort *)
  269.                    fName : FnString;                       (* data file name *)
  270.                    sPtr : SortFieldList;         (* list holding sort fields *)
  271.                    size : DataSizeRange);        (* size of data record
  272.                                                     (logical record)         *)
  273. type
  274.     DataArrayPtr = ^DataArray;
  275.     DataArray = Array[DataSizeRange] of Byte;
  276.  
  277. var
  278.     datPtr1,
  279.     datPtr2 : DataArrayPtr;
  280.     cnt : LrNumber;
  281.  
  282. (*\*)
  283.    (* This routine will swap the position of two entries in a logical record
  284.       list.  To function properly, the lrPos1 must be the relative position of
  285.       entry one and lrPos2 must be the relative position of entry two.
  286.       Relative position means position in the list.  If a list has 100 entries,
  287.       the first entry is number 1 and the last entry is number 100.  If the
  288.       second and third entries were to be swapped you should pass in 2 for
  289.       lrPos1 and 3 for lrPos2.                                               *)
  290.  
  291.     procedure SwapLr(lrPos1 : LrNumber;
  292.                      lrPos2 : LrNumber);
  293.  
  294.     var
  295.         pgPos1,
  296.         pgPos2 : LRArrayRange;
  297.         pr1,
  298.         pr2 : PrNumber;
  299.         tempLr1,
  300.         tempLr2 : LrNumber;
  301.  
  302.         begin
  303.         pr1 := DesiredPage(lrPos1);
  304.         pgPos1 := DesiredPosition(lrPos1);
  305.         pr2 := DesiredPage(lrPos2);
  306.         pgPos2 := DesiredPosition(lrPos2);
  307.         if lrLst.currPage <> pr1 then
  308.             begin
  309.             FetchPage(lrLst.fName,pr1,lrLst.page);
  310.             lrLst.currPage := pr1;
  311.             end;
  312.         tempLr1 := lrLst.lrArray[pgPos1];
  313.         if lrLst.currPage <> pr2 then
  314.             begin
  315.             FetchPage(lrLst.fName,pr2,lrLst.page);
  316.             lrLst.currPage := pr2;
  317.             end;
  318.         tempLr2 := lrLst.lrArray[pgPos2];
  319.         lrLst.lrArray[pgPos2] := tempLr1;
  320.         if lrLst.currPage <> pr1 then
  321.             begin
  322.             StorePage(lrLst.fName,lrLst.currPage,lrLst.page);
  323.             FetchPage(lrLst.fName,pr1,lrLst.page);
  324.             lrLst.currPage := pr1;
  325.             end;
  326.         lrLst.lrArray[pgPos1] := tempLr2;
  327.         if (lrLst.count > LRARRAYSIZE) then
  328.             begin
  329.             StorePage(lrLst.fName,lrLst.currPage,lrLst.page);
  330.             end;
  331.         end;                                        (* end of SwapLr routine *)
  332.  
  333. (*\*)
  334.     (* This routine will return the logical record number corresponding to the
  335.        position (lrPos) in the logical record list.  This routine allows
  336.        retrieval using the linked list as a psuedo array.                    *)
  337.  
  338.     function GetLrUsingPosition(lrPos : LrNumber) : LrNumber;
  339.  
  340.     var
  341.         pgPos : LRArrayRange;
  342.         pr : PrNumber;
  343.  
  344.         begin
  345.         pr := DesiredPage(lrPos);
  346.         pgPos := DesiredPosition(lrPos);
  347.         if lrLst.currPage <> pr then
  348.             begin
  349.             FetchPage(lrLst.fName,pr,lrLst.page);
  350.             lrLst.currPage := pr;
  351.             end;
  352.         GetLrUsingPosition := lrLst.lrArray[pgPos];
  353.         end;                            (* end of GetLrUsingPosition routine *)
  354.  
  355. (*\*)
  356.     (* This routine will compare logical record lr1 with logical record lr2.
  357.        The routine will LESSTHAN if lr1 is less than lr2 for the sort fields in
  358.        sPtr.   It will return EQUALTO if the records are equal for those sort
  359.        fields.  It will return GREATERTHAN if lr1 is greater than lr2 for those
  360.        sort fields.                                                          *)
  361.  
  362.     function CompareRecords(lr1 : LrNumber;
  363.                             lr2 : LrNumber) : Comparison;
  364.  
  365.     var
  366.         tempPtr : SortFieldList;
  367.         done : Boolean;
  368.         result : Comparison;
  369.  
  370.         begin
  371.         GetALogicalRecord(fName,lr1,datPtr1^,size);
  372.         GetALogicalRecord(fName,lr2,datPtr2^,size);
  373.         tempPtr := sPtr;
  374.         done := FALSE;
  375.         while not done do
  376.             begin
  377.             result := CompareValues(datPtr1^[tempPtr^.pos],
  378.                                     datPtr2^[tempPtr^.pos],
  379.                                     tempPtr^.vType);
  380.             case result of
  381.                 LESSTHAN :
  382.                     begin
  383.                     done := TRUE;
  384.                     CompareRecords := LESSTHAN;
  385.                     end;
  386.                 GREATERTHAN :
  387.                     begin
  388.                     done := TRUE;
  389.                     CompareRecords := GREATERTHAN;
  390.                     end;
  391.                 EQUALTO :
  392.                     begin
  393.                     tempPtr := tempPtr^.next;
  394.                     if tempPtr = NIL then
  395.                         begin
  396.                         done := TRUE;
  397.                         CompareRecords := EQUALTO;
  398.                         end;
  399.                     end;
  400.                 end;                                (* end of case statement *)
  401.             end;
  402.         end;                                (* end of CompareRecords routine *)
  403.  
  404. (*\*)
  405.     (* This routine will compare logical record lr with pivot record.  The
  406.        pivot record must be loaded into the location pointed to by datPtr2
  407.        prior to calling this routine.  Aside from the above, this routine
  408.        works much like the CompareRecords routine above.                     *)
  409.  
  410.     function CompareRecordToPivot(lr : LrNumber) : Comparison;
  411.  
  412.     var
  413.         tempPtr : SortFieldList;
  414.         done : Boolean;
  415.         result : Comparison;
  416.  
  417.         begin
  418.         GetALogicalRecord(fName,lr,datPtr1^,size);
  419.         tempPtr := sPtr;
  420.         done := FALSE;
  421.         while not done do
  422.             begin
  423.             result := CompareValues(datPtr1^[tempPtr^.pos],
  424.                                     datPtr2^[tempPtr^.pos],
  425.                                     tempPtr^.vType);
  426.             case result of
  427.                 LESSTHAN :
  428.                     begin
  429.                     done := TRUE;
  430.                     CompareRecordToPivot := LESSTHAN;
  431.                     end;
  432.                 GREATERTHAN :
  433.                     begin
  434.                     done := TRUE;
  435.                     CompareRecordToPivot := GREATERTHAN;
  436.                     end;
  437.                 EQUALTO :
  438.                     begin
  439.                     tempPtr := tempPtr^.next;
  440.                     if tempPtr = NIL then
  441.                         begin
  442.                         done := TRUE;
  443.                         CompareRecordToPivot:= EQUALTO;
  444.                         end;
  445.                     end;
  446.                 end;                                (* end of case statement *)
  447.             end;
  448.         end;                          (* end of CompareRecordToPivot routine *)
  449.  
  450. (*\*)
  451.     (* This routine performs a standard bubble sort on elements of a list
  452.        ranging from i to j where i is the location of the first element
  453.        and j is the location of the last element                             *)
  454.  
  455.     procedure BubbleSort(i : LrNumber;
  456.                          j : LrNumber);
  457.  
  458.     var
  459.         tempLr1,
  460.         tempLr2,
  461.         ii,
  462.         jj : LrNumber;
  463.  
  464.         begin
  465.         for ii := i to j - 1 do
  466.             begin
  467.             for jj := j downto ii + 1 do
  468.                 begin
  469.                 tempLr1 := GetLrUsingPosition(jj);
  470.                 tempLr2 := GetLrUsingPosition(jj-1);
  471.                 if CompareRecords(tempLr1,tempLr2) = LESSTHAN then
  472.                     begin
  473.                     SwapLr(jj,jj-1);
  474.                     end;
  475.                 end;
  476.             end;
  477.         end;                                    (* end of BubbleSort routine *)
  478.  
  479. (*\*)
  480.     (* This routine will the logical record number for the record which is the
  481.        pivot record.  The algorithm has been altered to enhance performance.
  482.        Once it is determined that not all records are equal and therefore do
  483.        not need sorted, the first and middle records are compared rather than
  484.        the first and first one that differs.  This will enhance performance. *)
  485.  
  486.     function FindPivot(i : LrNumber;
  487.                        j : LrNumber) : LrNumber;
  488.  
  489.     var
  490.         k,
  491.         mid,
  492.         tempLrMid,
  493.         tempLr1,
  494.         tempLr2 : LrNumber;
  495.  
  496.         begin
  497.         tempLr1 := GetLrUsingPosition(i);
  498.         for k := i + 1 to j do
  499.             begin           (* try to find record not equal to the first one *)
  500.             tempLr2 := GetLrUsingPosition(k);
  501.             if CompareRecords(tempLr1,tempLr2) <> EQUALTO then
  502.                 begin
  503.                 mid := (i + j) div 2;                       (* middle record *)
  504.                 tempLrMid := GetLrUsingPosition(mid);
  505.                 case CompareRecords(tempLr1,tempLrMid) of
  506.                     LESSTHAN    : FindPivot := tempLrMid;
  507.                     EQUALTO     : begin
  508.                                   case CompareRecords(tempLr1,tempLr2) of
  509.                                       LESSTHAN : FindPivot := tempLr2;
  510.                                       GREATERTHAN : FindPivot := tempLr1;
  511.                                       end;
  512.                                   end;
  513.                     GREATERTHAN : FindPivot := tempLr1;
  514.                     end;
  515.                 Exit;
  516.                 end;
  517.             end;
  518.         FindPivot := 0;                          (* different keys not found *)
  519.         end;                                     (* end of FindPivot routine *)
  520.  
  521. (*\*)
  522.     function Partition(i : LrNumber;
  523.                        j : LrNumber;
  524.                        pivotLr : LrNumber) : LrNumber;
  525.  
  526.     var
  527.         l,
  528.         r,
  529.         tempLr1 : LrNumber;
  530.  
  531.         begin
  532.         l := i;
  533.         r := j;
  534.         GetALogicalRecord(fName,pivotLr,datPtr2^,size);
  535.         while TRUE do
  536.             begin
  537.             tempLr1 := GetLrUsingPosition(l);
  538.             while CompareRecordToPivot(tempLr1) = LESSTHAN do
  539.                 begin
  540.                 l := l + 1;
  541.                 tempLr1 := GetLrUsingPosition(l);
  542.                 end;
  543.             tempLr1 := GetLrUsingPosition(r);
  544.             while CompareRecordToPivot(tempLr1) <> LESSTHAN do
  545.                 begin
  546.                 r := r - 1;
  547.                 tempLr1 := GetLrUsingPosition(r);
  548.                 end;
  549.             if l > r then
  550.                 begin
  551.                 Partition := l;
  552.                 Exit;
  553.                 end;
  554.             SwapLr(l,r);
  555.             end;
  556.         Partition := l;
  557.         end;                                     (* end of Partition routine *)
  558.  
  559. (*\*)
  560.     (* This routine will recursively sort a list of logical record numbers
  561.        using a combination of a quicksort and a bubble sort algorithm.  The
  562.        list to be is passed in as lrLst.  fName is the name of the data file,
  563.        sPtr is the pointer to the sort list, size is the size of the data file
  564.        logical records and i and j are positions in the logical record to
  565.        sort.                                                                 *)
  566.  
  567.     procedure QuickSort(i : LrNumber;
  568.                         j : lrNumber);
  569.  
  570.     var
  571.         k,
  572.         pivotLr : LrNumber;
  573.  
  574.         begin
  575.         pivotLr := FindPivot(i,j);
  576.         if pivotLr <> 0 then
  577.             begin
  578.             k := Partition(i,j,pivotLr);
  579.             if (k - i) < BUBBLELIMIT then
  580.                 begin
  581.                 BubbleSort(i,k-1);
  582.                 end
  583.             else
  584.                 begin
  585.                 QuickSort(i,k-1);
  586.                 end;
  587.             if (j - k) < BUBBLELIMIT then
  588.                 begin
  589.                 BubbleSort(k,j);
  590.                 end
  591.             else
  592.                 begin
  593.                 QuickSort(k,j);
  594.                 end;
  595.             end;
  596.         end;                                     (* end of QuickSort routine *)
  597.  
  598.  
  599.     begin
  600.     cnt := GetCountLr(lrLst);
  601.     if cnt > 1 then
  602.         begin
  603.         if MaxAvail >= size then
  604.             begin
  605.             GetMem(datPtr1,size);
  606.             if MaxAvail >= size then
  607.                 begin
  608.                 GetMem(datPtr2,size);
  609.                 QuickSort(1,cnt);
  610.                 FreeMem(datPtr1,size);
  611.                 end;
  612.             FreeMem(datPtr2,size);
  613.             end;
  614.         end;
  615.     end;                                          (* end of SortList routine *)
  616.  
  617.  
  618. end.                                                     (* end of Sort Unit *)
  619.