home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pmos2002.zip / SRC / FILESORT.MOD < prev    next >
Text File  |  1996-10-02  |  17KB  |  451 lines

  1. IMPLEMENTATION MODULE FileSort;
  2.  
  3.         (********************************************************)
  4.         (*                                                      *)
  5.         (*      In-place file sort using the QuickSort method   *)
  6.         (*                                                      *)
  7.         (*  Programmer:         P. Moylan                       *)
  8.         (*  Last edited:        2 October 1996                  *)
  9.         (*  Status:             Working                         *)
  10.         (*                                                      *)
  11.         (********************************************************)
  12.  
  13. FROM Trace IMPORT
  14.     (* proc *)  TraceOn, InTrace, OutTrace;
  15.  
  16. FROM SYSTEM IMPORT
  17.     (* type *)  ADDRESS;
  18.  
  19. FROM Files IMPORT
  20.     (* type *)  File,
  21.     (* proc *)  SetPosition, ReadRecord, WriteRecord;
  22.  
  23. FROM IOErrorCodes IMPORT
  24.     (* type *)  ErrorCode;
  25.  
  26. FROM QuickSortModule IMPORT
  27.     (* type *)  CompareProc,
  28.     (* proc *)  QuickSort;
  29.  
  30. FROM LowLevel IMPORT
  31.     (* proc *)  AddOffset, Copy;
  32.  
  33. FROM Storage IMPORT
  34.     (* proc *)  ALLOCATE, DEALLOCATE;
  35.  
  36. (************************************************************************)
  37.  
  38. CONST CacheSize = 2048;
  39.  
  40. TYPE
  41.     Cache = RECORD
  42.                 first, last, dirty1, dirty2: RecordNumber;
  43.                 dp: ADDRESS;
  44.             END (*RECORD*);
  45.  
  46.     FileDescriptor = RECORD
  47.                         file: File;
  48.                         eltsize, offset: CARDINAL;
  49.                         greaterorequal: CompareProc;
  50.                         cache: Cache;
  51.                      END (*RECORD*);
  52.  
  53.     EltPointer = ADDRESS;
  54.  
  55. (************************************************************************)
  56. (*                      BASIC I/O OPERATIONS                            *)
  57. (************************************************************************)
  58.  
  59. PROCEDURE ReadBlock (VAR (*IN*) FD: FileDescriptor;  N1, N2: RecordNumber;
  60.                                                         dest: EltPointer);
  61.  
  62.     (* Reads record numbers N1..N2, inclusive, into a buffer starting   *)
  63.     (* at location 'dest'.                                              *)
  64.  
  65.     VAR status: ErrorCode;  actual: CARDINAL;
  66.  
  67.     BEGIN
  68.         InTrace ("ReadBlock");
  69.         WITH FD DO
  70.             status := SetPosition (file, offset + eltsize*N1);
  71.             status := ReadRecord (file, dest, eltsize*(N2-N1+1), actual);
  72.         END (*WITH*);
  73.         OutTrace ("ReadBlock");
  74.     END ReadBlock;
  75.  
  76. (************************************************************************)
  77.  
  78. PROCEDURE WriteBlock (VAR (*IN*) FD: FileDescriptor; src: EltPointer;
  79.                                                 N1, N2: RecordNumber);
  80.  
  81.     (* Writes from a buffer starting at location 'src' to records       *)
  82.     (* number N1 to N2 inclusive.                                       *)
  83.  
  84.     VAR status: ErrorCode;
  85.  
  86.     BEGIN
  87.         InTrace ("WriteBlock");
  88.         WITH FD DO
  89.             status := SetPosition (file, offset + eltsize*N1);
  90.             status := WriteRecord (file, src, eltsize*(N2-N1+1));
  91.         END (*WITH*);
  92.         OutTrace ("WriteBlock");
  93.     END WriteBlock;
  94.  
  95. (************************************************************************)
  96. (*                           CACHE MANAGEMENT                           *)
  97. (* The cache here is a "load on demand" cache rather than the more      *)
  98. (* conventional cache which holds recently read records.  Our approach  *)
  99. (* is to cache the low-numbered records of the subfile currently        *)
  100. (* being sorted, which is consistent with an algorithm which, roughly   *)
  101. (* speaking, sorts the bottom half of a range before starting work on   *)
  102. (* the top half.                                                        *)
  103. (************************************************************************)
  104.  
  105. PROCEDURE FlushCache (VAR (*IN*) FD: FileDescriptor;  N: RecordNumber);
  106.  
  107.     (* Writes back any unwritten records, up to and including record    *)
  108.     (* number N, back to disk.                                          *)
  109.  
  110.     BEGIN
  111.         InTrace ("FlushCache");
  112.         WITH FD DO
  113.             WITH cache DO
  114.                 IF N > dirty2 THEN
  115.                     N := dirty2;
  116.                 END (*IF*);
  117.                 IF N >= dirty1 THEN
  118.                     (* We must flush out records dirty1..N *)
  119.                     WriteBlock (FD, AddOffset(dp,
  120.                         eltsize*VAL(CARDINAL,dirty1-first)), dirty1, N);
  121.                     dirty1 := N+1;
  122.                 END (*IF*);
  123.             END (*WITH*);
  124.         END (*WITH*);
  125.         OutTrace ("FlushCache");
  126.     END FlushCache;
  127.  
  128. (************************************************************************)
  129.  
  130. PROCEDURE LoadCache (VAR (*IN*) FD: FileDescriptor;
  131.                                         low, high: RecordNumber): BOOLEAN;
  132.  
  133.     (* Puts records low..high inclusive into the cache, if they will    *)
  134.     (* fit.  If they don't fit, loads as many as possible starting from *)
  135.     (* record number low.  Returns TRUE iff everything fitted.          *)
  136.  
  137.     VAR result: BOOLEAN;
  138.  
  139.     BEGIN
  140.         InTrace ("LoadCache");
  141.         WITH FD DO
  142.  
  143.             (* Special case: sometimes it will happen that not even     *)
  144.             (* one record will fit in the cache.                        *)
  145.  
  146.             IF CacheSize < eltsize THEN
  147.                 OutTrace ("LoadCache");
  148.                 RETURN FALSE;
  149.             END (*IF*);
  150.  
  151.             (* Work out whether everything will fit.  If not, adjust    *)
  152.             (* high downwards.                                          *)
  153.  
  154.             IF eltsize*(high-low+1) <= CacheSize THEN
  155.                 result := TRUE;
  156.             ELSE
  157.                 result := FALSE;
  158.                 high := CacheSize DIV eltsize + low - 1;
  159.             END (*IF*);
  160.  
  161.             (* Clean out everything below 'low' from the cache *)
  162.  
  163.             IF low > 0 THEN
  164.                 FlushCache (FD, low-1);
  165.             END (*IF*);
  166.             WITH cache DO
  167.  
  168.                 (* If the leading part of what we want is already in    *)
  169.                 (* the cache, move it to the beginning.                 *)
  170.  
  171.                 IF (first <= last) AND (low >= first) AND (low <= last) THEN
  172.                     IF low > first THEN
  173.                         Copy (AddOffset(dp, eltsize*VAL(CARDINAL,low-first)),
  174.                                  dp, eltsize*VAL(CARDINAL,last-low+1));
  175.                         first := low;
  176.                     END (*IF*);
  177.                     low := last + 1;
  178.  
  179.                 ELSE
  180.  
  181.                     (* In all other cases, empty the cache completely   *)
  182.                     (* and make a fresh beginning.  Note that we don't  *)
  183.                     (* bother to check for cases like low<first and     *)
  184.                     (* high>first, because we know that data will flow  *)
  185.                     (* through the cache only in one direction.         *)
  186.  
  187.                     FlushCache (FD, last);
  188.                     last := 0;  first := 1;
  189.  
  190.                 END (*IF*);
  191.  
  192.                 (* At this stage, either the cache is empty or it       *)
  193.                 (* contains a part of what we want.  In either case,    *)
  194.                 (* the remainder is sure to fit.                        *)
  195.  
  196.                 IF first > last THEN
  197.                     ReadBlock (FD, low, high, dp);
  198.                     first := low;
  199.                     last := high;
  200.                 ELSE
  201.                     IF high >= low THEN
  202.                         ReadBlock (FD, low, high,
  203.                                 AddOffset(dp,eltsize*VAL(CARDINAL,low-first)));
  204.                     END (*IF*);
  205.                     IF high > last THEN
  206.                         last := high;
  207.                     END (*IF*);
  208.                 END (*IF*);
  209.             END (*WITH*);
  210.         END (*WITH*);
  211.         OutTrace ("LoadCache");
  212.         RETURN result;
  213.     END LoadCache;
  214.  
  215. (************************************************************************)
  216.  
  217. PROCEDURE MarkDirty (VAR (*IN*) FD: FileDescriptor;  N: RecordNumber);
  218.  
  219.     (* Notes that record number N needs to be written back to disk      *)
  220.     (* from the cache.                                                  *)
  221.  
  222.     BEGIN
  223.         InTrace ("MarkDirty");
  224.         WITH FD.cache DO
  225.             IF dirty2 < dirty1 THEN
  226.                 dirty1 := N;  dirty2 := N;
  227.             ELSIF N < dirty1 THEN dirty1 := N;
  228.             ELSIF N > dirty2 THEN dirty2 := N;
  229.             END (*IF*);
  230.         END (*WITH*);
  231.         OutTrace ("MarkDirty");
  232.     END MarkDirty;
  233.  
  234. (************************************************************************)
  235.  
  236. PROCEDURE AddressInCache (VAR (*IN*) FD: FileDescriptor;
  237.                                         index: RecordNumber): EltPointer;
  238.  
  239.     (* Returns the address in memory of element 'index', if it is in    *)
  240.     (* the cache; otherwise returns NIL.                                *)
  241.  
  242.     BEGIN
  243.         InTrace ("AddressInCache");
  244.         OutTrace ("AddressInCache");
  245.         WITH FD DO
  246.             WITH cache DO
  247.                 IF (first > last) OR (index < first) OR (index > last) THEN
  248.                     RETURN NIL;
  249.                 ELSE
  250.                     RETURN AddOffset (dp, eltsize*VAL(CARDINAL,index-first));
  251.                 END (*IF*);
  252.             END (*WITH*);
  253.         END (*WITH*);
  254.     END AddressInCache;
  255.  
  256. (************************************************************************)
  257. (*                      I/O THROUGH CACHE WHERE POSSIBLE                *)
  258. (************************************************************************)
  259.  
  260. PROCEDURE GetRecord (VAR (*IN*) FD: FileDescriptor; index: RecordNumber;
  261.                                                         dest: EltPointer);
  262.  
  263.     (* Reads record number 'index' of the file into a buffer starting   *)
  264.     (* at location 'dest'.                                              *)
  265.  
  266.     VAR p: EltPointer;
  267.  
  268.     BEGIN
  269.         InTrace ("GetRecord");
  270.         p := AddressInCache (FD, index);
  271.         IF p <> NIL THEN
  272.             Copy (p, dest, FD.eltsize);
  273.         ELSE
  274.             ReadBlock (FD, index, index, dest);
  275.         END (*IF*);
  276.         OutTrace ("GetRecord");
  277.     END GetRecord;
  278.  
  279. (************************************************************************)
  280.  
  281. PROCEDURE PutRecord (VAR (*IN*) FD: FileDescriptor; src: EltPointer;
  282.                                                 index: RecordNumber);
  283.  
  284.     (* Writes from a buffer starting at location 'src' to record        *)
  285.     (* number 'index' of the file.                                      *)
  286.  
  287.     VAR p: EltPointer;
  288.  
  289.     BEGIN
  290.         p := AddressInCache (FD, index);
  291.         IF p <> NIL THEN
  292.             Copy (src, p, FD.eltsize);
  293.             MarkDirty (FD, index);
  294.         ELSE
  295.             WriteBlock (FD, src, index, index);
  296.         END (*IF*);
  297.     END PutRecord;
  298.  
  299. (************************************************************************)
  300. (*                      THE OVERALL SORTING ALGORITHM                   *)
  301. (************************************************************************)
  302.  
  303. PROCEDURE Partition ( VAR (*IN*) FD: FileDescriptor;
  304.                                 low: RecordNumber;
  305.                                 VAR (*OUT*) mid: RecordNumber;
  306.                                 high: RecordNumber);
  307.  
  308.     (* By shuffling elements as necessary, ensures the property         *)
  309.     (*          R[j] <= v       for low <= j < mid                      *)
  310.     (*          R[mid] = v                                              *)
  311.     (*          R[j] >= v       for mid < j <= high                     *)
  312.     (* where R[j] represents record number j of the file, mid is the    *)
  313.     (* function result, and v is some unspecified value chosen by the   *)
  314.     (* procedure.                                                       *)
  315.  
  316.     VAR up, down: RecordNumber;
  317.         ptemp, pmid: EltPointer;
  318.  
  319.     BEGIN
  320.         InTrace ("Partition");
  321.         down := low;  up := high;  mid := (down + up) DIV 2;
  322.  
  323.         ALLOCATE (ptemp, FD.eltsize);
  324.         ALLOCATE (pmid, FD.eltsize);
  325.         GetRecord (FD, mid, pmid);
  326.  
  327.         (* v is pmid^.  The following loop maintains the invariants:    *)
  328.         (*      R[j] <= v       for low <= j < down                     *)
  329.         (*      R[j] >= v       for up < j <= high                      *)
  330.         (* We exit the outer loop when down >= mid and up <= mid.       *)
  331.         (* Note that v=pmid^ is the value that should be stored as      *)
  332.         (* R[mid], but to avoid redundant store and load operations as  *)
  333.         (* mid changes we don't actually store this value back until    *)
  334.         (* the final exit from the loop.  During loop execution, mid    *)
  335.         (* refers to a "hole" in which a value has not yet been stored. *)
  336.         (* Note also that ptemp^ holds either R[down] or R[up],         *)
  337.         (* depending on whether we're adjusting down or up at the time. *)
  338.  
  339.         LOOP
  340.             GetRecord (FD, down, ptemp);
  341.             WHILE (down < mid) AND FD.greaterorequal (pmid, ptemp) DO
  342.                 INC (down);
  343.                 GetRecord (FD, down, ptemp);
  344.             END (*WHILE*);
  345.  
  346.             IF down < mid THEN
  347.                 PutRecord (FD, ptemp, mid);
  348.                 mid := down;
  349.                 INC (down);
  350.             END (*IF*);
  351.  
  352.             (* Note that down >= mid at this point.     *)
  353.  
  354.             GetRecord (FD, up, ptemp);
  355.             WHILE (up > mid) AND FD.greaterorequal (ptemp, pmid) DO
  356.                 DEC (up);
  357.                 GetRecord (FD, up, ptemp);
  358.             END (*WHILE*);
  359.  
  360.             IF up <= mid THEN EXIT(*LOOP*) END(*IF*);
  361.  
  362.             PutRecord (FD, ptemp, mid);
  363.             mid := up;
  364.             DEC (up);
  365.  
  366.         END (*LOOP*);
  367.  
  368.         PutRecord (FD, pmid, mid);
  369.         DEALLOCATE (pmid, FD.eltsize);
  370.         DEALLOCATE (ptemp, FD.eltsize);
  371.         OutTrace ("Partition");
  372.  
  373.     END Partition;
  374.  
  375. (************************************************************************)
  376.  
  377. PROCEDURE SmallSort (VAR (*IN*) FD: FileDescriptor;  low, high: RecordNumber);
  378.  
  379.     (* Sorts the subfile of records low..high inclusive, where we are   *)
  380.     (* guaranteed that the entire range is in the cache.                *)
  381.  
  382.     VAR p: EltPointer;
  383.  
  384.     BEGIN
  385.         InTrace ("SmallSort");
  386.         p := AddressInCache (FD, low);
  387.         QuickSort (p^, VAL(CARDINAL,high-low), FD.eltsize, FD.greaterorequal);
  388.         MarkDirty (FD, low);
  389.         MarkDirty (FD, high);
  390.         OutTrace ("SmallSort");
  391.     END SmallSort;
  392.  
  393. (************************************************************************)
  394.  
  395. PROCEDURE Sort ( VAR (*IN*) FD: FileDescriptor;  low, high: RecordNumber);
  396.  
  397.     (* Sorts the subfile of records low..high inclusive.        *)
  398.  
  399.     VAR mid: RecordNumber;
  400.  
  401.     BEGIN
  402.         InTrace ("Sort");
  403.         IF LoadCache (FD, low, high) THEN
  404.             SmallSort (FD, low, high);
  405.         ELSE
  406.             Partition (FD, low, mid, high);
  407.             IF mid > low+1 THEN Sort (FD, low, mid-1) END(*IF*);
  408.             IF high > mid+1 THEN Sort (FD, mid+1, high) END(*IF*);
  409.         END (*IF*);
  410.         FlushCache (FD, high);
  411.         OutTrace ("Sort");
  412.     END Sort;
  413.  
  414. (************************************************************************)
  415. (*                         THE END-USER VERSION                         *)
  416. (************************************************************************)
  417.  
  418. PROCEDURE InplaceSort (f: File;  from, to: RecordNumber;
  419.                         EltSize, Offset: CARDINAL;  GE: CompareProc);
  420.  
  421.     (* In-place sort of part of a file.  We sort record numbers         *)
  422.     (* from..to inclusive.  EltSize is the element size; Offset is the  *)
  423.     (* number of bytes (zero, in most cases) before record number 0 in  *)
  424.     (* the file; and GE is a user-supplied function to compare elements *)
  425.     (* at two specified addresses.                                      *)
  426.  
  427.     VAR FD: FileDescriptor;
  428.  
  429.     BEGIN
  430.         WITH FD DO
  431.             file := f;
  432.             eltsize := EltSize;
  433.             offset := Offset;
  434.             greaterorequal := GE;
  435.             WITH cache DO
  436.                 first := MAX(RecordNumber);  last := 0;
  437.                 dirty1 := 1;  dirty2 := 0;
  438.                 ALLOCATE (dp, CacheSize);
  439.             END (*WITH*);
  440.         END (*WITH*);
  441.         Sort (FD, from, to);
  442.         DEALLOCATE (FD.cache.dp, CacheSize);
  443.     END InplaceSort;
  444.  
  445. (************************************************************************)
  446.  
  447. BEGIN
  448.     (*TraceOn (0, 10, 0, 50, 1);*)
  449. END FileSort.
  450.  
  451.