home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS - Coast to Coast / simteldosarchivecoasttocoast2.iso / ddjmag / ddj8911.zip / HELLER.LST < prev    next >
File List  |  1989-10-04  |  20KB  |  753 lines

  1. _EXTENSIBLE HASHING_
  2. by Steve Heller
  3.  
  4.  
  5. [LISTING ONE]
  6.  
  7. {KRAM.PAS - A Keyed Random Access Method for Turbo Pascal 5.0.}
  8. {Copyright (c) 1989 by Chrysalis Software Corp.  All rights reserved.}
  9. {Written by Steve Heller.}
  10.  
  11. {$V-}
  12.  
  13. program KramTest;
  14.  
  15. uses Crt;
  16.  
  17. const
  18.   PARAMSIZE = 128; {the size of the parameter block, in bytes}
  19.  
  20.   DATASIZE = 2048; {the size of a data block, in bytes}
  21.  
  22.   INDEXCOUNT = 1024; {the number of index entries.  Must be a power of 2.}
  23.   INDEXSIZE = INDEXCOUNT * Sizeof(integer); {the size of the index block}
  24.  
  25. type
  26.   KramDataType = array[1..DATASIZE] of byte;
  27.   KramDataPtr = ^KramDataType;
  28.   KramIndexType = array[1..INDEXCOUNT] of integer;
  29.   KramIndexPtr = ^KramIndexType;
  30.  
  31. {The variant record type below is used so that we can add new}
  32. {parameters if needed, without changing the size of the parameter}
  33. {block in the data file.}
  34.   KramParamType = record
  35.                     case integer of
  36.                       0: (
  37.  
  38. {the items below are saved from one run to another}
  39.                           KeyLength:integer;
  40.                           DataLength:integer;
  41.                           HighBlock:integer;
  42. {the items above are saved from one run to another}
  43.  
  44. {the ones below are valid only during the current run}
  45.                           CurrentBlock:integer;
  46.                           BlockModified:boolean;
  47.                           DataPtr:KramDataPtr;
  48.                           IndexPtr:KramIndexPtr;
  49.                          );
  50.                       1: (Dummy:array[1..PARAMSIZE] of byte);
  51.                   end;
  52.   KramParamPtr = ^KramParamType;
  53.  
  54.   FileRec = RECORD
  55.           Handle : word;
  56.           Mode   : word;
  57.           RecSize: word;
  58.           Private: array [1..26] OF byte;
  59.  
  60. {note: this is a nonstandard declaration}
  61.           UserData: array [1..4] OF pointer;
  62.  
  63.           Name   : array [0..79] OF char;
  64.           END;
  65.  
  66.  
  67.  
  68.  
  69. FUNCTION HashCode(Key : string):longint;
  70.  
  71. {Use this function to calculate a pseudo-random number based on the}
  72. {value of its argument.  The result is used to determine which index}
  73. {entry will be used to access the record with the given key.  The}
  74. {algorithm used here is appropriate for ASCII key values, as it uses}
  75. {the low five bits of each byte of the key.}
  76.  
  77. VAR
  78.   i : integer;
  79.   result : longint;
  80.   temp1 : longint;
  81.   temp2 : longint;
  82.   bytetemp : byte;
  83.  
  84. BEGIN
  85.  
  86.   result := 0;
  87.  
  88.   FOR i := 1 TO length(Key) DO
  89.     BEGIN
  90.       temp1 := result shl 5;
  91.       temp2 := result shr 27;
  92.       result := temp1 or temp2;
  93.       bytetemp := ord(Key[i]);
  94.       result := result xor bytetemp;
  95.     END;
  96.  
  97.   HashCode := result;
  98.  
  99. END;
  100.  
  101.  
  102.  
  103.  
  104. PROCEDURE SeekBlock(VAR KramFile : file; BlockNum : integer);
  105.  
  106. {Use this procedure to position the file pointer to a particular data}
  107. {block.  In order to do this, we must skip the parameter block and }
  108. {the index block.  Also note that the first data block is #1.}
  109.  
  110. VAR
  111.   BlockPosition : longint;
  112.  
  113. BEGIN
  114.  
  115.   BlockPosition := PARAMSIZE + INDEXSIZE + (DATASIZE * (BlockNum-1));
  116.   Seek(KramFile,BlockPosition);
  117.  
  118. END;
  119.  
  120.  
  121.  
  122.  
  123. PROCEDURE KramInit(FileName : string; KeyLength, DataLength: integer);
  124.  
  125. {Use this procedure to initialize a new KRAM file.  It sets up the}
  126. {key length and the data length according to the input arguments.}
  127. {The high data block number is set to 1 and data block #1 is}
  128. {initialized to zeroes (an empty block).  The index block is set to}
  129. {all 1's, so that all accesses will go to the empty data block.}
  130.  
  131. VAR
  132.   KramFile:file;
  133.   Index : KramIndexType;
  134.   Data : KramDataType;
  135.   Params : KramParamType;
  136.   i : integer;
  137.  
  138.  
  139. BEGIN
  140.  
  141.      Assign(KramFile,FileName);
  142.      Rewrite(KramFile,1);
  143.  
  144.      FillChar(Params.Dummy,SizeOf(Params.Dummy),0);
  145.  
  146.      Params.KeyLength := KeyLength;
  147.      Params.DataLength := DataLength;
  148.      
  149. {the highest data block number in use is #1}
  150.      Params.HighBlock := 1;  
  151.  
  152.      BlockWrite(KramFile,Params,SizeOf(Params));
  153.  
  154. {Initialize the index block to all 1's, as only data block #1 exists}
  155.  
  156.      FOR i := 1 TO INDEXCOUNT DO
  157.        Index[i] := 1;
  158.  
  159.      BlockWrite(KramFile,Index,SizeOf(Index));
  160.  
  161. {Initialize the first data block to all zeroes}
  162.  
  163.      FOR i := 1 TO DATASIZE DO
  164.        Data[i] := 0;
  165.  
  166.      BlockWrite(KramFile,Data,SizeOf(Data));
  167.  
  168.      Close(KramFile);
  169.  
  170. END;
  171.  
  172.  
  173.  
  174.  
  175. PROCEDURE KramOpen(VAR KramFile:file;KramFileName:string);
  176.  
  177. {Use this procedure to open the file.  It reads the parameter block}
  178. {and the index block from the beginning of the file and allocates}
  179. {space for the data block.}
  180.  
  181. VAR
  182.    RecsRead : integer;
  183.    ParamPtr : KramParamPtr;
  184.  
  185. BEGIN
  186.  
  187.   Assign(KramFile,KramFileName);
  188.   Reset(KramFile,1);
  189.  
  190.   New(ParamPtr);
  191.  
  192.   KramParamPtr(FileRec(KramFile).UserData[1]) := ParamPtr;
  193.  
  194.   BlockRead(KramFile,ParamPtr^,SizeOf(KramParamType),RecsRead);
  195.   IF RecsRead <> SizeOf(KramParamType) THEN
  196.      BEGIN
  197.        WriteLn('Invalid KRAM file: ',KramFileName);
  198.        Halt(1);
  199.      END;
  200.  
  201.   New(ParamPtr^.IndexPtr);
  202.   New(ParamPtr^.DataPtr);
  203.   ParamPtr^.CurrentBlock := 0;
  204.  
  205.   BlockRead(KramFile,ParamPtr^.IndexPtr^,
  206.   SizeOf(KramIndexType),RecsRead);
  207.   
  208.   IF RecsRead <> SizeOf(KramIndexType) THEN
  209.      BEGIN
  210.        WriteLn('Invalid KRAM file: ',KramFileName);
  211.        Halt(1);
  212.      END;
  213.  
  214. END;
  215.  
  216.  
  217.  
  218.  
  219. PROCEDURE KramClose(var KramFile:file);
  220.  
  221. {Use this procedure to close the file after use. It also deallocates}
  222. {the dynamic storage used for the parameter, index, and data blocks.}
  223.  
  224. {IMPORTANT NOTE: If you have modified the file, by adding records,}
  225. {for example, you MUST close the file to ensure that all the records}
  226. {have been written to the file.}
  227.  
  228. VAR
  229.   ParamPtr : KramParamPtr;
  230.  
  231. BEGIN
  232.  
  233.   ParamPtr := KramParamPtr(FileRec(KramFile).UserData[1]);
  234.   SeekBlock(KramFile,ParamPtr^.CurrentBlock);
  235.   BlockWrite(KramFile,ParamPtr^.DataPtr^,DATASIZE);
  236.  
  237.   Dispose(ParamPtr^.DataPtr);
  238.   Dispose(ParamPtr^.IndexPtr);
  239.   Dispose(ParamPtr);
  240.   
  241.   Close(KramFile);
  242.  
  243. END;
  244.  
  245.  
  246.  
  247. FUNCTION KramAdd(VAR KramFile : file; KeyValue : string;
  248.                   DataValue : string):boolean;
  249.  
  250. {Use this procedure to add records to a KRAM file that has been opened}
  251. {by KramOpen.  You must supply the file pointer that was returned by}
  252. {KramOpen in "KramFile",the key in "KeyValue", and the data in}
  253. {"DataValue".  The algorithm is known as "extensible hashing".}
  254.  
  255. VAR
  256.   ParamPtr : KramParamPtr;
  257.   LowDataPtr : KramDataPtr;
  258.   HighDataPtr : KramDataPtr;
  259.   HashVal  : longint;
  260.   BlockNumber : integer;
  261.   TempBlockNumber : integer;
  262.   RecordLength : integer;
  263.   i,j,k    : integer;
  264.   SlotFound: boolean;
  265.   FoundFirst : boolean;
  266.   FoundLast : boolean;
  267.   FirstBlock : integer;
  268.   LastBlock : integer;
  269.   MidBlock : integer;
  270.   KramFileName : string;
  271.   TempKeyValue : string;
  272.   OldOffset : integer;
  273.   LowOffset : integer;
  274.   HighOffset : integer;
  275.   Duplicate : boolean;
  276.   KeepLooking : boolean;
  277.  
  278. BEGIN
  279.  
  280.   ParamPtr := KramParamPtr(FileRec(KramFile).UserData[1]);
  281.  
  282.   KramFileName := FileRec(KramFile).Name;
  283.  
  284.   RecordLength := ParamPtr^.KeyLength + ParamPtr^.DataLength;
  285.  
  286.   REPEAT
  287.  
  288.     HashVal := HashCode(KeyValue) and (INDEXCOUNT - 1);
  289.  
  290.     BlockNumber := ParamPtr^.IndexPtr^[HashVal+1];
  291.  
  292.     IF ParamPtr^.CurrentBlock <> BlockNumber THEN
  293.       BEGIN
  294.         IF (ParamPtr^.BlockModified)
  295.         and (ParamPtr^.CurrentBlock <> 0) THEN
  296.           BEGIN
  297.             ParamPtr^.BlockModified := FALSE;
  298.             SeekBlock(KramFile,ParamPtr^.CurrentBlock);
  299.             BlockWrite(KramFile,ParamPtr^.DataPtr^,DATASIZE);
  300.           END;
  301.         SeekBlock(KramFile,BlockNumber);
  302.         BlockRead(KramFile,ParamPtr^.DataPtr^,DATASIZE);
  303.         ParamPtr^.CurrentBlock := BlockNumber;
  304.       END;
  305.  
  306.  
  307.     Duplicate := FALSE;
  308.     KeepLooking := TRUE;
  309.     SlotFound := FALSE;
  310.     i := 1;
  311.     OldOffset := 1;
  312.  
  313.     {initialize length of temporary string used for comparison}
  314.     TempKeyValue[0] := char(ParamPtr^.KeyLength);
  315.  
  316.     WHILE KeepLooking AND (i <= DATASIZE DIV RecordLength) DO
  317.       BEGIN
  318.         IF ParamPtr^.DataPtr^[OldOffset] = 0 THEN
  319.           BEGIN
  320.             KeepLooking := FALSE;
  321.             SlotFound := TRUE;
  322.           END
  323.         ELSE
  324.           BEGIN
  325.             Move(ParamPtr^.DataPtr^[OldOffset],TempKeyValue[1],ParamPtr^.KeyLength);
  326.             IF TempKeyValue = KeyValue THEN
  327.               BEGIN
  328.                 Duplicate := TRUE;
  329.                 KeepLooking := FALSE;
  330.               END
  331.             ELSE
  332.               BEGIN
  333.                 OldOffset := OldOffset + RecordLength;
  334.                 i := i + 1;
  335.               END;
  336.           END;
  337.       END;
  338.  
  339.     IF SlotFound THEN
  340.       BEGIN
  341.         Move(KeyValue[1],ParamPtr^.DataPtr^[OldOffset],
  342.         ParamPtr^.KeyLength);
  343.  
  344.         Move(DataValue[1],
  345.         ParamPtr^.DataPtr^[OldOffset+ParamPtr^.KeyLength],
  346.         ParamPtr^.DataLength);
  347.  
  348.         ParamPtr^.BlockModified := TRUE;
  349.       END
  350.     ELSE IF Duplicate = FALSE THEN
  351.       BEGIN
  352. {First we must determine how many index entries are affected}
  353. {by the split}
  354.         FoundFirst := FALSE;
  355.         FoundLast := FALSE;
  356.         LastBlock := INDEXCOUNT;
  357.         FOR i := 1 TO INDEXCOUNT DO
  358.           BEGIN
  359.             IF (ParamPtr^.IndexPtr^[i] = BlockNumber)
  360.             and (not FoundFirst) THEN
  361.               BEGIN
  362.                 FirstBlock := i;
  363.                 FoundFirst := TRUE;
  364.               END;
  365.             IF (ParamPtr^.IndexPtr^[i] <> BlockNumber) 
  366.             and FoundFirst and (not FoundLast) THEN
  367.               BEGIN
  368.                 LastBlock := i - 1;
  369.                 FoundLast := TRUE;
  370.               END
  371.           END;
  372.         IF FirstBlock >= LastBlock THEN 
  373. {we are out of room}
  374.           BEGIN
  375.             WriteLn('KRAM file: ',KramFileName,' is full.');
  376.             Halt(1);
  377.           END;
  378.  
  379. {Now we have to allocate another block for the split.}
  380.         ParamPtr^.HighBlock := ParamPtr^.HighBlock + 1;
  381.         MidBlock := (FirstBlock + LastBlock - 1) DIV 2;
  382.  
  383. {We will assign the items that have the higher hash code}
  384. {to the new block}
  385.         FOR i := MidBlock+1 TO LastBlock DO
  386.           ParamPtr^.IndexPtr^[i] := ParamPtr^.HighBlock;
  387.  
  388. {Now we have to go through all the items in the block to be split}
  389. {and assign them to the old block or the new one according to their}
  390. {new hash codes, 1 bit longer than the previous ones.  An extra}
  391. {temporary block makes this easier.}
  392.         New(LowDataPtr);
  393.         New(HighDataPtr);
  394.  
  395.         FillChar(LowDataPtr^,SizeOf(LowDataPtr^),0);
  396.         FillChar(HighDataPtr^,SizeOf(HighDataPtr^),0);
  397.  
  398.         OldOffset := 1;
  399.         LowOffset := 1;
  400.         HighOffset := 1;
  401.         FOR i := 1 TO DATASIZE DIV RecordLength DO
  402.           BEGIN
  403.             Move(ParamPtr^.DataPtr^[OldOffset],TempKeyValue[1],
  404.             ParamPtr^.KeyLength);
  405.  
  406.             TempKeyValue[0] := char(ParamPtr^.KeyLength);
  407.             HashVal := HashCode(TempKeyValue) and (INDEXCOUNT - 1);
  408.             TempBlockNumber := ParamPtr^.IndexPtr^[HashVal+1];
  409.             IF TempBlockNumber = BlockNumber THEN 
  410. {send to the lower one}
  411.               BEGIN
  412.                 Move(ParamPtr^.DataPtr^[OldOffset],
  413.                 LowDataPtr^[LowOffset],RecordLength);
  414.  
  415.                 LowOffset := LowOffset + RecordLength;
  416.               END
  417.             ELSE
  418.               BEGIN
  419.                 Move(ParamPtr^.DataPtr^[OldOffset],
  420.                 HighDataPtr^[HighOffset],RecordLength);
  421.  
  422.                 HighOffset := HighOffset + RecordLength;
  423.               END;
  424.             OldOffset := OldOffset + RecordLength;
  425.           END;
  426.  
  427.         SeekBlock(KramFile,BlockNumber);
  428.         BlockWrite(KramFile,LowDataPtr^,DATASIZE);
  429.  
  430.         SeekBlock(KramFile,ParamPtr^.HighBlock);
  431.         BlockWrite(KramFile,HighDataPtr^,DATASIZE);
  432.  
  433.         Dispose(LowDataPtr);
  434.         Dispose(HighDataPtr);
  435.  
  436. {Make sure the same block isn't used the the next time we have to}
  437. {expand the file.  Also note that the data block is out of date.}
  438.         ParamPtr^.CurrentBlock := 0;
  439.         Seek(KramFile,0);
  440.         BlockWrite(KramFile,ParamPtr^,SizeOf(KramParamType));
  441.         BlockWrite(KramFile,ParamPtr^.IndexPtr^,
  442.         SizeOf(KramIndexType));
  443.  
  444.       END;
  445.  
  446.   UNTIL SlotFound or Duplicate;
  447.  
  448.   IF Duplicate THEN
  449.     KramAdd := FALSE
  450.   ELSE
  451.     KramAdd := TRUE;
  452.  
  453. END;
  454.  
  455.  
  456.  
  457.  
  458. FUNCTION KramRead(var KramFile:file; KeyValue: string;
  459.                   var DataValue: string):boolean;
  460.  
  461. {Use this function to read records from a KRAM file that has been}
  462. {opened by KramOpen.  You must supply the key in "KeyValue" and the}
  463. {file pointer that was returned by KramOpen in "KramFile".  The data}
  464. {stored under that key will be returned in "DataValue".  The return}
  465. {value is TRUE if the record was found, and FALSE if it wasn't.}
  466.  
  467. VAR
  468.   ParamPtr : KramParamPtr;
  469.   HashVal  : longint;
  470.   BlockNumber : integer;
  471.   RecordLength : integer;
  472.   i,j,k    : integer;
  473.   SlotFound: boolean;
  474.   KeepLooking : boolean;
  475.   KramFileName : string;
  476.   TempKeyValue : string;
  477.   DataOffset : integer;
  478.  
  479. BEGIN
  480.  
  481.   ParamPtr := KramParamPtr(FileRec(KramFile).UserData[1]);
  482.  
  483.   KramFileName := FileRec(KramFile).Name;
  484.  
  485.   RecordLength := ParamPtr^.KeyLength + ParamPtr^.DataLength;
  486.  
  487.   HashVal := HashCode(KeyValue) and (INDEXCOUNT - 1);
  488.  
  489.   BlockNumber := ParamPtr^.IndexPtr^[HashVal+1];
  490.  
  491.   IF ParamPtr^.CurrentBlock <> BlockNumber THEN
  492.     BEGIN
  493.       IF (ParamPtr^.BlockModified)
  494.       and (ParamPtr^.CurrentBlock <> 0) THEN
  495.         BEGIN
  496.           ParamPtr^.BlockModified := FALSE;
  497.           SeekBlock(KramFile,ParamPtr^.CurrentBlock);
  498.           BlockWrite(KramFile,ParamPtr^.DataPtr^,DATASIZE);
  499.         END;
  500.       SeekBlock(KramFile,BlockNumber);
  501.       BlockRead(KramFile,ParamPtr^.DataPtr^,DATASIZE);
  502.       ParamPtr^.CurrentBlock := BlockNumber;
  503.     END;
  504.  
  505.  
  506.     KeepLooking := TRUE;
  507.     SlotFound := FALSE;
  508.     i := 1;
  509.     DataOffset := 1;
  510.  
  511.     {initialize length of temporary string used for comparison}
  512.     TempKeyValue[0] := char(ParamPtr^.KeyLength);
  513.  
  514.     WHILE KeepLooking AND (i <= DATASIZE DIV RecordLength) DO
  515.       BEGIN
  516.         IF ParamPtr^.DataPtr^[DataOffset] = 0 THEN
  517.           KeepLooking := FALSE
  518.         ELSE
  519.           BEGIN
  520.             Move(ParamPtr^.DataPtr^[DataOffset],TempKeyValue[1],ParamPtr^.KeyLength);
  521.             IF TempKeyValue = KeyValue THEN
  522.               BEGIN
  523.                 SlotFound := TRUE;
  524.                 KeepLooking := FALSE;
  525.               END
  526.             ELSE
  527.               BEGIN
  528.                 DataOffset := DataOffset + RecordLength;
  529.                 i := i + 1;
  530.               END;
  531.           END;
  532.       END;
  533.  
  534.  
  535.   IF SlotFound THEN
  536.     BEGIN
  537.       Move(ParamPtr^.DataPtr^[DataOffset+ParamPtr^.KeyLength],
  538.       DataValue[1],ParamPtr^.DataLength);
  539.  
  540.       DataValue[0] := char(ParamPtr^.DataLength);
  541.       KramRead := TRUE;
  542.     END
  543.   ELSE
  544.     KramRead:= FALSE;
  545.  
  546. END;
  547.  
  548.  
  549.  
  550.  
  551. PROCEDURE KramInfo(var KramFile:file; var KeyLength: integer;
  552.                   var DataLength: integer);
  553.  
  554. {Use this procedure to get the key and data lengths from a KRAM file that has}
  555. {been opened by KramOpen.  You must supply the file pointer that was returned}
  556. {by KramOpen in "KramFile".}
  557.  
  558. VAR
  559.   ParamPtr : KramParamPtr;
  560.  
  561. BEGIN
  562.  
  563.   ParamPtr := KramParamPtr(FileRec(KramFile).UserData[1]);
  564.  
  565.   KeyLength := ParamPtr^.KeyLength;
  566.  
  567.   DataLength := ParamPtr^.DataLength;
  568.  
  569. END;
  570.  
  571.  
  572.  
  573.  
  574. VAR
  575.   KramTestFile : file;
  576.   FileName : string;
  577.   DataFileName : string;
  578.   KeyLength : integer;
  579.   DataLength : integer;
  580.   KramFile : file;
  581.   Ok : boolean;
  582.   InputFile : text;
  583.   Key : string;
  584.   RecNum : integer;
  585.   TestRecNum : integer;
  586.   RecNumStr : string;
  587.   InputFileBuf : array [1..10240] of char;
  588.   Create : string;
  589.   Status : integer;
  590.   ReadStatus : boolean;
  591.   AddStatus : boolean;
  592.   Temp : string;
  593.   Data : string;
  594.   TempKey : string;
  595.   TempData : string;
  596.  
  597. BEGIN
  598.  
  599.      ClrScr;
  600.     WriteLn('KRAM.PAS - A Keyed Random Access Method for Turbo Pascal 5.0.');
  601.     WriteLn('Copyright (c) 1989 by Chrysalis Software Corp.  All rights reserved.');
  602.     WriteLn('Written by Steve Heller.');
  603.      WriteLn;
  604.     WriteLn('This program is shareware: if you find it useful, you should');
  605.     WriteLn('register it by sending a check in the amount of $39.95, ');
  606.     WriteLn('payable to Chrysalis Software Corporation, to the following address');
  607.     WriteLn;
  608.     WriteLn('Chrysalis Software Corporation');
  609.     WriteLn('P. O. Box 0335');
  610.     WriteLn('Baldwin, NY 11510');
  611.     WriteLn;
  612.     WriteLn('Registered users will receive an updated version of the program,');
  613.     WriteLn('incorporating all of the optimizations and extensions mentioned');
  614.     WriteLn('in this article.');
  615.     WriteLn;
  616.     Write('Please hit ENTER to continue.');
  617.     ReadLn(FileName);
  618.     ClrScr;
  619.  
  620.      Write('Please enter the name of the data file to be used for input: ');
  621.      ReadLn(DataFileName);
  622.  
  623.      Write('Please enter the name of the KRAM file: ');
  624.      ReadLn(FileName);
  625.  
  626.      Write('Create the KRAM file (Y/N): ');
  627.      ReadLn(Create);
  628.      IF (Create[1] = 'Y') or (Create[1] = 'y') THEN
  629.        BEGIN
  630.  
  631.          Write('Please enter the key length: ');
  632.          ReadLn(KeyLength);
  633.          Write('Please enter the data length: ');
  634.          ReadLn(DataLength);
  635.  
  636.          Assign(InputFile,DataFileName);
  637.          Reset(InputFile);
  638.          SetTextBuf(InputFile,InputFileBuf);
  639.  
  640.          KramInit(FileName,KeyLength,DataLength);
  641.          KramOpen(KramTestFile,FileName);
  642.  
  643.          RecNum := 0;
  644.          AddStatus := TRUE;
  645.          WHILE NOT EOF(InputFile) DO
  646.            BEGIN
  647.              IF RecNum Mod 10 = 0 THEN
  648.                Write(RecNum:5);
  649.              RecNum := RecNum + 1;
  650.              ReadLn(InputFile,Temp);
  651.              Key := Copy(Temp,1,KeyLength);
  652.              Data := Copy(Temp,KeyLength+1,length(Temp));
  653.              AddStatus := KramAdd(KramTestFile,Key,Data);
  654.              IF AddStatus = FALSE THEN
  655.                BEGIN
  656.                  WriteLn;
  657.                  WriteLn('Unable to add key: ',Key);
  658.                END;
  659.            END;
  660.  
  661.          WriteLn;
  662.          Close(InputFile);
  663.          KramClose(KramTestFile);
  664.        END;
  665.  
  666.        Assign(InputFile,DataFileName);
  667.        Reset(InputFile);
  668.        SetTextBuf(InputFile,InputFileBuf);
  669.  
  670.        KramOpen(KramTestFile,FileName);
  671.  
  672.        KramInfo(KramTestFile,KeyLength,DataLength);
  673.  
  674.        RecNum := 0;
  675.        WHILE NOT EOF(InputFile) DO
  676.          BEGIN
  677.            IF RecNum Mod 10 = 0 THEN
  678.              Write(RecNum:5);
  679.            RecNum := RecNum + 1;
  680.            ReadLn(InputFile,Temp);
  681.  
  682.            Key := copy(Temp,1,KeyLength);
  683.            TempData := copy(Temp,KeyLength+1,DataLength);
  684.  
  685.            ReadStatus := KramRead(KramTestFile,Key,Data);
  686.            IF Not ReadStatus THEN
  687.              BEGIN
  688.                WriteLn;
  689.                WriteLn('Key ',TempKey,' not found.');
  690.              END;
  691.            IF TempData <> Data THEN
  692.              BEGIN
  693.                WriteLn;
  694.                WriteLn('Record number ',RecNum,' does not match');
  695.              END;
  696.          END;
  697.  
  698.        WriteLn;
  699.  
  700.        Close(InputFile);
  701.        KramClose(KramTestFile);
  702.  
  703. END.
  704.  
  705. [LISTING TWO]
  706.  
  707. {KRAMDATA.PAS - generates data for KRAM testing}
  708. {890226:1245}
  709.  
  710. VAR
  711.   s : String[255];
  712.   t : Text;
  713.   n : LongInt;
  714.   i : LongInt;
  715.   j : Integer;
  716.   KeyLength : Integer;
  717.   DataLength : Integer;
  718.   FName   : String[80];
  719.  
  720. BEGIN
  721.  
  722.   Randomize;
  723.  
  724.   WriteLn;
  725.  
  726.   Write('Name of data file to be generated: ');
  727.   ReadLn(FName);
  728.  
  729.   Write('Number of items to generate: ');
  730.   ReadLn(n);
  731.  
  732.   Write('Length of Keys: ');
  733.   ReadLn(KeyLength);
  734.  
  735.   Write('Length of Data: ');
  736.   ReadLn(DataLength);
  737.  
  738.   Assign(t,Fname);
  739.   Rewrite(t);
  740.  
  741.   FOR i := 1 TO N DO
  742.     BEGIN
  743.       IF i = 1000*int(i/1000) THEN Write(i:10);
  744.       s := '';
  745.       FOR j := 1 TO KeyLength DO
  746.         s := s + chr(random(26)+65);
  747.       Write(t,s);
  748.       WriteLn(t,i:DataLength);
  749.     END;
  750.  
  751.   Close(t);
  752.  
  753. END.