home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / SRCHMEMP.ZIP / SRCHMEMP.PAS
Encoding:
Pascal/Delphi Source File  |  1986-08-24  |  56.3 KB  |  2,050 lines

  1. {$G512,P512,D-}  { Compiler directives to allow I/O redirection }
  2. program SrchMem;
  3. {
  4.  
  5.   Compare variations of 2 string search algorithms, the brute force and
  6.   Boyer-Moore methods.
  7.  
  8.  
  9.  
  10.     Implemented by Paul Wolberg.  Copyright (C) 1986.
  11.  
  12.     References:
  13.  
  14.     "A Fast String Searching Algorithm" by Boyer and Moore,
  15.     Communications of the ACM, October 1977, volume 20, number 10,
  16.     pages 762-772.
  17.  
  18.     "Algorithms" by Robert Sedgewick, published by Addison-Wesley,
  19.     1984, pages 241-252.
  20.  
  21.     "Fast Pattern Matching In Strings" by Knuth, Morris and Pratt.
  22.     SIAM Journal of Computing, Vol 6, No. 2, June 1977
  23.  
  24.     "A Correct Preprocessing Algorithm for Boyer-Moore String Searching"
  25.     by Wojciech Rytter
  26.     SIAM Journal of Computing, Vol 9, No. 3, August 1980
  27.  
  28.  
  29.  
  30.   The following notation is used:
  31.  
  32.   Target is the character string which is to be searched for the pattern
  33.  
  34.   Pattern is the pattern being searched for in Target
  35.  
  36.  
  37.  
  38.   The algorithms are:
  39.  
  40.     0 Brute Force
  41.     1 Boyer-Moore using just the delta1 table
  42.     2 Boyer-Moore using the fast/slow loop form of delta1
  43.     3 Boyer-Moore using both the delta1 and delta2 tables
  44.     4 B-M with delta1, counting total skips
  45.     5 B-M with delta1 and delta2, counting total skips
  46.     6 Create delta2 table using brute force method.
  47.     7 Create delta2 table using Rytter's correction to Knuth.
  48.  
  49.  
  50.  
  51.   Program structure:
  52.  
  53.   The GetProb routine gets the algorithm to use, the file to search and
  54.   the pattern to search for from the command line.
  55.  
  56.   The Setup routine opens the file to be searched, reads it into an
  57.   array of strings in memory
  58.   and initializes the elapsed timer to the current time.
  59.  
  60.   The selected algorithm is used to search the text for "TotalTimes" times,
  61.   in order to have sufficiently long bench marks for comparisons.
  62.  
  63.   Each algorithm has 2 routines, a DO routine and a search routine. The
  64.   Do routine calls the search routine once for each line in the file.
  65.   It increments a counter if the search routine found the pattern in the line.
  66.   The Boyer-Moore algorithms also call one or more routines to set up tables
  67.   used by that set of algorithms.
  68.  
  69.   After the text has been searched the Cleanup routine computes and displays
  70.   the elapsed time. Turbo Pascal compiler directives are used that allow
  71.   redirection of the results to a disk file.
  72.  
  73.   Algorithms 0 to 3 are used to compare the brute force method and variations
  74.   of the Boyer-Moore method by comparing timings for the methods.
  75.  
  76.   Algorithms 4 and 5 are used to compare Boyer-Moore with and without the
  77.   delta 2 table. These comparisons are based on counts of the number of
  78.   times the different tables are used and the aggregate pattern shifts
  79.   for the 2 methods. The timings are not relevant for these 2 comparisons,
  80.   since they include time used to increment the counters.
  81.  
  82.   Algorithms 6 and 7 are used to compare 2 methods of constructing the delta 2
  83.   table. Constructing the delta 2 table involves searching the pattern for
  84.   substrings of itself, hence it is a string search problem as well. Algorithm
  85.   6 uses a brute force method, whereas algorithm 7 uses a method similar to
  86.   the delta 2 part of Boyer-Moore. See Knuth and Rytter for details on this.
  87.   There are no DO or search routines for algorithms 6 and 7, the delta 2
  88.   construction routines are called directly. The comparisons are based
  89.   on timings.
  90.  
  91.   There is also a routine to display the pattern and its delta 2 table.
  92.   It is used for debugging only. Calls to it are deleted for comparison
  93.   runs.
  94.  
  95.   Usage: SRCHMEMP algorithm file pattern
  96.          algorithm = 0, brute force
  97.  
  98.          algorithm = 1, Boyer-Moore with just delta1
  99.          algorithm = 2, Boyer-Moore with fast/slow loop form of delta1
  100.          algorithm = 3, Boyer-Moore with both delta1 and delta2 tables
  101.          algorithm = 4, Boyer-Moore with delta1, counting total skips
  102.          algorithm = 5, Boyer-Moore with delta1 and delta2, counting total skips
  103.          algorithm = 6, Create delta2 table using brute force method
  104.          algorithm = 7, Create delta2 table using Rytter's correction to Knuth
  105.  
  106.          file is the name of the file to be searched
  107.  
  108.          pattern is the pattern to search for
  109.          spaces can be embedded in pattern by using @n, where n
  110.          is a single digit that is the number of spaces to insert.
  111.          Ex: "A@3B" on the command line becomes "A   B" in the program.
  112.  
  113.  
  114. }
  115.  
  116. Const
  117.   MAXALG   = 7;      { Maximum algorithm code. Number of algorithms
  118.                        implemented = MAXALG + 1, since they are numbered
  119.                        from zero. }
  120.   MAXSTR   = 90;     { Maximum number of characters in various strings }
  121.   ASCIIlen = 255;    { Number of characters in the extended ASCII set }
  122.   MAXLINE  = 650;    { Maximum number of lines to read from input file }
  123.   TOTTIMES = 10;     { Total number of times to search the text }
  124.  
  125.  
  126. Type
  127.   Line = string[MAXSTR];
  128.   Del1Array = Array[0..ASCIIlen] of Integer; { Array type for Boyer-Moore
  129.                                                delta1 table }
  130.   Del2Array = Array[0..MAXSTR] of Integer;   { Array type for Boyer-Moore
  131.                                                delta2 table }
  132.  
  133.  
  134.  
  135. Var
  136.   LARGE      : Integer; { Arbitrarily large value for FAST/SLOW versions }
  137.   TargetName : Line;    { Name of file containing text to search }
  138.   TargetVar  : Text;    { File variable for the file to search }
  139.   NumLines   : Integer; { Number of lines in TargetName }
  140.   TargetList : array[1..MAXLINE] of Line;    { Array of strings to search }
  141.   Pattern    : Line;    { The pattern to search for }
  142.   Algorithm  : Integer; { The algorithm to use }
  143.   TimesCount : Integer; { Number of times text has been searched thus far }
  144.   BegTime    : Real;    { Starting time for the search, in seconds }
  145.   NumFound   : Integer; { Number of lines in TargetFile containing Pattern }
  146.   D1Count    : Real;    { Number of times delta1 table used }
  147.   D2Count    : Real;    { Number of times delta2 table used }
  148.   Skip1Count : Real;    { Aggregate skip due to delta1 }
  149.   Skip2Count : Real;    { Aggregate skip due to delta2 }
  150.  
  151.  
  152.  
  153. {------------------------------------------------------------------------------
  154. Procedure   expand
  155. expand escape sequences in pattern to blanks
  156. -------------------------------------------------------------------------------
  157.  
  158.     ENTRY:
  159.           CmdPat is the pattern from the command line. It may have
  160.           escape sequences to be expanded to blanks.
  161.  
  162.  
  163.     EXIT:
  164.           Pattern is the CmdPat with escape sequences replaced with blanks.
  165.  
  166.     NOTE:
  167.           An escape sequence to be replaced with blanks is an "@" followed
  168.           by a single digit. Two "@"'s are reduced to single "@". An "@"
  169.           followed by any other character is left alone.
  170.           Examples:
  171.  
  172.           CmdPat:  ABC@3XYZ    CmdPat:  ABC@@3XYZ   CmdPat:  ABC@XYZ
  173.           Pattern: ABC   XYZ   Pattern: ABC@3XYZ    Pattern: ABC@XYZ
  174.  
  175. ------------------------------------------------------------------------------}
  176.  
  177. procedure expand(CmdPat: Line; var Pattern: Line);
  178.  
  179. var
  180.   state    : Integer; { The state of the pattern blank expander. }
  181.   numblank : Integer; { The number of blanks the escape sequence expands to. }
  182.   Code     : Integer; { Return code from the Val function. }
  183.   pj       : Char;    { The current character of pattern from command line. }
  184.   i,j      : Integer; { Loop counters. }
  185.  
  186.  
  187. begin { procedure expand }
  188.  
  189. Pattern :=  '';
  190.  
  191. state := 0;
  192. for j := 1 to Length(CmdPat) do
  193.   begin
  194.   pj := CmdPat[j];
  195.  
  196.   {
  197.     STATE 0
  198.     In an escape sequence. If the character is not an "@"
  199.     then copy it to Pattern. If it is an "@", then change to STATE 1,
  200.     in an escape sequence.
  201.   }
  202.   if (state = 0) then
  203.     begin
  204.     if (pj <> '@') then
  205.       begin
  206.       Pattern := Pattern + pj;
  207.       end
  208.     else
  209.       begin
  210.       state := 1;
  211.       end;
  212.     end { state 0 }
  213.  
  214.   {
  215.     STATE 1
  216.     In an escape sequence. If the character is a digit, append that
  217.     number of blanks to the end of Pattern. If it is not a digit and
  218.     not a blank, append an "@" (the previous character from CmdPat) and
  219.     then the character. If the character is an "@" then there have been
  220.     two "@"'s in a row. Append a single "@" to Pattern, since two
  221.     consecutive "@"'s reduce to a single "@".
  222.   }
  223.   else
  224.     begin
  225.     if (pj <> '@') then
  226.       begin
  227.       val(pj, numblank, code);
  228.       if (code = 0) then
  229.         begin
  230.         { A digit. Append that many blanks to end of Pattern }
  231.         for i := 1 to numblank do Pattern := Pattern + ' ';
  232.         end
  233.       else
  234.         begin
  235.         { Not a digit or "@", append "@" (previous char) and current char }
  236.         Pattern := Pattern + '@' + pj;
  237.         end;
  238.       state := 0;
  239.       end
  240.     else
  241.       begin
  242.       { An "@". Two "@"'s reduce to a single "@". }
  243.       Pattern := Pattern + pj;
  244.       state := 0;
  245.       end;
  246.  
  247.     end; { state 1 }
  248.  
  249.   end; { for i }
  250.  
  251.  
  252.  
  253. end;  { procedure expand }
  254.  
  255.  
  256.  
  257. {------------------------------------------------------------------------------
  258.   Get the problem parameters from the command line.
  259.   -------------------------------------------------------------------------------
  260.  
  261.  
  262.     ENTRY:
  263.           The command should contain 3 parameters:
  264.           An algorithm code, the name of a file to search, a pattern to
  265.           search for.
  266.  
  267.     EXIT:
  268.     Algorithm = 0 for Brute force method
  269.                 1 for Boyer-Moore with just delta1 table
  270.                 2 for Boyer-Moore with fast/slow loop form of delta1
  271.                 3 for Boyer-Moore with both delta1 and delta2 tables
  272.                 4 for B-M with delta1, counting total skips
  273.                 5 for B-M with delta1 and delta2, counting total skips
  274.                 6 Create delta2 table using brute force method.
  275.                 7 Create delta2 table using Rytter's correction to Knuth.
  276.  
  277.  
  278.     TargetName  is the name of the file to search.
  279.  
  280.     Pattern     is the pattern to search for.
  281.  
  282. ------------------------------------------------------------------------------}
  283.  
  284.  
  285. procedure GetProb (var Algorithm: Integer; var TargetName, Pattern: Line);
  286.  
  287. var
  288.   Code     : Integer; { Return code from the Val function. }
  289.  
  290.  
  291. begin {procedure GetProb}
  292.  
  293.  
  294.  
  295. if ParamCount <> 3 then
  296.   begin
  297.   WriteLn('Expected 3 parameters, found ', ParamCount);
  298.   WriteLn('Usage: SRCHMEMP algorithm file pattern');
  299.   WriteLn('       algorithm = 0, brute force');
  300.   WriteLn('       algorithm = 1, Boyer-Moore with just delta1');
  301.   WriteLn('       algorithm = 2, Boyer-Moore with fast/slow loop form of delta1');
  302.   WriteLn('       algorithm = 3, Boyer-Moore with both delta1 and delta2 tables');
  303.   WriteLn('       algorithm = 4, Boyer-Moore with delta1, counting total skips');
  304.   WriteLn('       algorithm = 5, Boyer-Moore with delta1 and delta2, counting total skips');
  305.   WriteLn('       algorithm = 6, Create delta2 table using brute force method.');
  306.   WriteLn('       algorithm = 7, Create delta2 table using Rytter''s correction to Knuth.');
  307.   WriteLn('       file is the name of the file to be searched');
  308.   WriteLn('       pattern is the pattern to search for');
  309.   WriteLn('       spaces can be embedded in pattern by using @n, where n');
  310.   WriteLn('       is a single digit that is the number of spaces to insert.');
  311.   WriteLn('       Ex: "A@3B" on the command line becomes "A   B" in the program.');
  312.   Halt;
  313.   end;
  314.  
  315. Val(ParamStr(1), Algorithm, Code);
  316. if (Code <> 0) or (Algorithm < 0) or (Algorithm > MAXALG) then
  317.   begin
  318.   WriteLn('The algorithm parameter is invalid.');
  319.   WriteLn('It should be a number between 0 and ', MAXALG, '.');
  320.   Halt;
  321.   end;
  322.  
  323. TargetName := ParamStr(2);
  324.  
  325. expand(ParamStr(3), Pattern);
  326.  
  327. end; {procedure GetProb}
  328.  
  329.  
  330. {------------------------------------------------------------------------------
  331.   Get the current time from MS-DOS, in seconds since the beginning of the day.
  332. -------------------------------------------------------------------------------
  333.  
  334.     ENTRY:
  335.  
  336.     EXIT:
  337.          A real return value that is the number of seconds from the
  338.          beginning of the day until now.
  339.  
  340. ------------------------------------------------------------------------------}
  341.  
  342. function GetTime :Real;
  343.  
  344.  
  345. type
  346.   Regpack = record
  347.               AX, BX, CX, DX, BP, SI, DI, ES, Flags: Integer;
  348.             end;
  349.  
  350. var
  351.   Regs    : Regpack;
  352.  
  353. begin { procedure GetTime }
  354.  
  355. with Regs do
  356.   begin
  357.   AX := $2C00;  { Place DOS system request for time into register AX }
  358.   MsDos(Regs);
  359.  
  360.   {
  361.     Time in seconds is hours times 3600 seconds per hour +
  362.     minutes times 60 seconds per minute + seconds + hundreths of seconds
  363.   }
  364.   GetTime := hi(CX)*3600.0 + lo(CX)*60.0 + hi(DX) + lo(DX)/100.0;
  365.   end; { with Regs}
  366.  
  367.  
  368. end;  { procedure GetTime }
  369.  
  370.  
  371. {------------------------------------------------------------------------------
  372.   Find position of 1st occurrence of Pattern in Target
  373. -------------------------------------------------------------------------------
  374.  
  375.     ENTRY:
  376.           Target  is the string to be searched.
  377.           Pattern is the pattern to search for.
  378.  
  379.           Target and Pattern are declared as call by reference (var) to
  380.           avoid having them copied on each call to PosBF.
  381.  
  382.  
  383.     EXIT:
  384.          An integer return value that is the position of the 1st
  385.          occurrence of Pattern in Target. If Pattern does not occur
  386.          in Target, the return value is zero.
  387.  
  388. ------------------------------------------------------------------------------}
  389.  
  390. function PosBF(var Target, Pattern: Line) :Integer;
  391.  
  392.  
  393. Label
  394.   NOTFOUND;
  395.  
  396. Var
  397.   Stop: Integer; {The last position in Target to look for Pat}
  398.   i:    Integer; {The current position in Target for the search}
  399.   j:    Integer; {The current position in Pat being compared to the
  400.                   corresponding position in Target}
  401.   Patlen: Integer; {The length of Pat}
  402.  
  403.  
  404. begin {function PosBF}
  405.  
  406.  
  407. {
  408.   Stop is the last position in Target to look for Pattern. Positions
  409.   past Stop are ignored because they would mean Pattern extends past
  410.   the end of Target. This includes the case when Pattern is longer
  411.   than Target. In that case, the for loop does zero iterations,
  412.   because Stop is less than 1, and a result of zero is returned.
  413. }
  414. Patlen := Length(Pattern);
  415. Stop := Length(Target) - Patlen + 1;
  416.  
  417.  
  418.   {
  419.     Test each character in Target as the starting position of Pattern.
  420.   }
  421.   for i := 1 to Stop do
  422.     begin
  423.  
  424.     {
  425.       Check for Pattern starting at position i of Target.
  426.       Keep looking as long as characters in Pattern match
  427.       characters in Target. If all match, we are done.
  428.       If a mismatch is found, then quit looking for Pattern
  429.       at position i of Target and look for it at postion i+1.
  430.     }
  431.     j := 1;
  432.     while (Pattern[j] = Target[j + (i-1)]) do
  433.       begin
  434.       if j < Patlen then
  435.         begin
  436.         j := j + 1;
  437.         end
  438.       else
  439.         begin
  440.         PosBF := i;
  441.         Exit;
  442.         end;
  443.       end; { while Pattern = Target }
  444.  
  445.     end; { for i }
  446.  
  447.   {
  448.     Pattern has not been found anywhere in Target.
  449.   }
  450.   PosBF := 0;
  451.  
  452. end; {function PosBF}
  453.  
  454.  
  455. {------------------------------------------------------------------------------
  456.   Search an array of strings for a pattern, using the brute force method.
  457. -------------------------------------------------------------------------------
  458.  
  459.     ENTRY:
  460.           TargetList is an array of strings to search.
  461.           NumLines is the number of elements in Target.
  462.           Pattern is the pattern to search for.
  463.  
  464.     EXIT:
  465.          Numfound is the number of lines in file TargetName that
  466.          contain Pattern.
  467.  
  468. ------------------------------------------------------------------------------}
  469.  
  470. procedure DoBruteForce(var NumFound: Integer);
  471.  
  472. var
  473.   PBF  : Integer; { Position of Pattern in Target as returned by posBF }
  474.   i    : Integer; { Loop counter }
  475.  
  476.  
  477. begin {procedure DoBruteForce}
  478.  
  479. for TimesCount := 1 to TOTTIMES do
  480.   begin
  481.  
  482.  
  483.   for i := 1 to NumLines do
  484.     begin
  485.     PBF := posBF(TargetList[i], Pattern);
  486.     If PBF > 0 then
  487.       begin
  488.       NumFound := NumFound + 1;
  489.       end;
  490.     end; { for i }
  491.  
  492.  
  493.   end; { for TimesCount }
  494.  
  495. end; {procedure DoBruteForce}
  496.  
  497. {------------------------------------------------------------------------------
  498.  Create a delta1 table for Pattern, for the Boyer-Moore string search method.
  499. -------------------------------------------------------------------------------
  500.  
  501.     ENTRY:
  502.           Pattern is the pattern for which the table is contructed.
  503.  
  504.     EXIT:
  505.          Delta1, a table that contains the distance of each extended
  506.          ASCII character from the right end of Pattern.
  507.  
  508.     The table assigns to each extended ASCII character its distance from
  509.     the right end of the pattern. If a character is not in the pattern
  510.     then its distance is the length of the pattern. If a character appears
  511.     more than once in the pattern, then its shortest distance is used.
  512.  
  513.     Example:
  514.     Pattern := 'ASAP'
  515.     Delta1['P'] := 0
  516.     Delta1['A'] := 1
  517.     Delta1['S'] := 2
  518.     Delta1 := 4 for all other characters
  519.  
  520. ------------------------------------------------------------------------------}
  521.  
  522. procedure MakeDelta1(Pattern: Line; var Delta1: Del1Array);
  523.  
  524.  
  525. var
  526.   Patlen    : Integer; { The pattern length }
  527.   i         : Integer; { a loop counter }
  528.  
  529. begin { procedure MakeDelta1 }
  530.  
  531. Patlen := Length(Pattern);
  532.  
  533. {
  534.   Initialize the table to the pattern length.
  535. }
  536. for i := 1 to ASCIIlen do
  537.   begin
  538.   Delta1[i] := Patlen;
  539.   end;
  540.  
  541. {
  542.   Scan the pattern from the left. For each character in Pattern,
  543.   store in Delta1 its position from the right end of Pattern.
  544. }
  545. for i := 1 to Patlen do
  546.   begin
  547.   Delta1[Ord(Pattern[i])] := Patlen - i;
  548.   end;
  549.  
  550. end;  { procedure MakeDelta1 }
  551.  
  552.  
  553. {------------------------------------------------------------------------------
  554.   search a string for a pattern using the Boyer-Moore method with just delta1
  555.   -------------------------------------------------------------------------------
  556.  
  557.     ENTRY:
  558.       Target  is the string to search within.
  559.       Pattern is the string to search for.
  560.       Delta1  is the Boyer-Moore delta1 table. It contains the distance
  561.               of each extended ASCII character from the right end of Pattern.
  562.               Characters that do not appear in Pattern have distance Patlen.
  563.  
  564.       Target, Pattern and Delta1 are declared as call by reference (var) to
  565.       avoid having them copied on each call to PosDelta1.
  566.  
  567.  
  568.     EXIT:
  569.       An integer return value which indicates the starting position of Pattern
  570.       in Target. It is zero if Pattern is not found.
  571.  
  572. ------------------------------------------------------------------------------}
  573.  
  574. function posDelta1(var Target, Pattern: Line; var Delta1: Del1Array): Integer;
  575.  
  576. var
  577.   Trglen   : Integer; { The length of Target }
  578.   Patlen   : Integer; { The length of Pattern }
  579.   i, j     : Integer; { Loop counters }
  580.   oldi     : Integer; { Character of Target compared with right end of
  581.                         Pattern. Used if Delta1 would cause a backward
  582.                         shift. }
  583.  
  584. begin { function posDelta1 }
  585.  
  586. Trglen := Length(Target);
  587. Patlen := Length(Pattern);
  588.  
  589.  
  590.  
  591. {
  592.   The outer loop. This positions Pattern in Target
  593.   for the next set of character comparisons. Note that if
  594.   Pattern is longer than Target (Patlen > Trglen) then the
  595.   outer loop is never executed and a value of zero is returned.
  596. }
  597. i := Patlen;
  598. while (i <= Trglen) do
  599.   begin
  600.   j := Patlen;
  601.   oldi := i;
  602.  
  603.   {
  604.     Inner loop. Compare Pattern to Target, starting at the right end of
  605.     Pattern (the essence of Boyer-Moore). If characters compare, decrement
  606.     pointers and compare next pair of characters. If we have decremented
  607.     to the 1st character of Pattern we are done and return the position
  608.     in string where the match occurred.
  609.   }
  610.  
  611.   while (Target[i] = Pattern[j]) do
  612.     begin
  613.     if (j > 1) then
  614.       begin
  615.       j := j - 1;
  616.       i := i - 1;
  617.       end
  618.     else
  619.       begin
  620.       posDelta1 := i;
  621.       Exit;
  622.       end;
  623.  
  624.     end; { while (Target[i] = Pattern[j]) }
  625.  
  626.   {
  627.     Nonmatching characters found. Use delta1 table to compute number
  628.     of characters to slide Pattern along Target. If Delta1 causes a backward
  629.     shift, then slide Pattern by 2 characters (it turns out that the minimum
  630.     shift is 2, not 1).
  631.   }
  632.   i := i + Delta1[Ord(Target[i])];
  633.   if (i <= oldi) then i := oldi + 2;
  634.  
  635. end; { while (i <= Trglen) }
  636.  
  637. posDelta1 := 0;
  638.  
  639. end;  { function posDelta1 }
  640.  
  641.  
  642. {------------------------------------------------------------------------------
  643.   Search an array of strings for a pattern, using the Boyer-Moore method
  644.   with just the delta1 table.
  645. -------------------------------------------------------------------------------
  646.  
  647.     ENTRY:
  648.           TargetList is an array of strings to search.
  649.           NumLines is the number of elements in Target.
  650.           Pattern is the pattern to search for.
  651.     EXIT:
  652.          Numfound is the number of lines in file TargetName that
  653.          contain Pattern.
  654.  
  655. ------------------------------------------------------------------------------}
  656.  
  657. procedure DoDelta1(var NumFound: Integer) ;
  658.  
  659.  
  660. var
  661.   Delta1   : Del1Array; { The delta 1 table }
  662.   i        : Integer;   { Loop counter }
  663.   PD1      : Integer;
  664.  
  665. begin {procedure DoDelta1}
  666.  
  667. {
  668.   Create the delta1 table.
  669. }
  670. MakeDelta1(Pattern, Delta1);
  671.  
  672.  
  673. for TimesCount := 1 to TOTTIMES do
  674.   begin
  675.  
  676.  
  677.   for i := 1 to NumLines do
  678.     begin
  679.     PD1 := posDelta1(TargetList[i], Pattern, Delta1);
  680.  
  681.     If PD1 > 0 then
  682.       Begin
  683.       NumFound := NumFound + 1;
  684.       end;
  685.     end; { for i }
  686.  
  687.  
  688.   end; { for TimesCount }
  689.  
  690. end; {procedure DoDelta1}
  691.  
  692.  
  693.  
  694. {------------------------------------------------------------------------------
  695.   Search a string for a pattern using the Boyer-Moore method with just delta1
  696.   Use the FAST-SLOW loop structure.
  697.   -------------------------------------------------------------------------------
  698.  
  699.     ENTRY:
  700.       Target  is the string to search within.
  701.       Pattern is the string to search for.
  702.       Delta1  is the Boyer-Moore delta1 table. It contains the distance
  703.               of each extended ASCII character from the right end of Pattern.
  704.               Characters that do not appear in Pattern have distance Patlen.
  705.  
  706.       Target, Pattern and Delta1 are declared as call by reference (var) to
  707.       avoid having them copied on each call to PosDelta1.
  708.  
  709.  
  710.     EXIT:
  711.       An integer return value which indicates the starting position of Pattern
  712.       in Target. It is zero if Pattern is not found.
  713.  
  714. ------------------------------------------------------------------------------}
  715.  
  716. function posDelta1F(var Target, Pattern: Line; var Delta1: Del1Array): Integer;
  717.  
  718. label
  719.   FAST, SLOW;               { Top of fast loop, top of slow loop }
  720.  
  721. var
  722.   Trglen   : Integer; { The length of Target }
  723.   Patlen   : Integer; { The length of Pattern }
  724.   i, j     : Integer; { Pointers to Target and Pattern }
  725.   oldi     : Integer; { Character of Target compared with right end of
  726.                         Pattern. Used if Delta1 would cause a backward
  727.                         shift. }
  728.   d1       : Integer; { Delta1 value when  mismatch occurrs in slow loop. }
  729.  
  730.  
  731. begin { function posDelta1F }
  732.  
  733. Trglen := Length(Target);
  734. Patlen := Length(Pattern);
  735. if (Patlen > Trglen) then
  736.   begin
  737.   posDelta1F := 0;
  738.   Exit;
  739.   end;
  740.  
  741. i := Patlen;
  742.  
  743.  
  744. {
  745.   The fast loop. This loop compares the right end of Pattern to the
  746.   corresponding character in Target. If they don't match, we skip
  747.   according to the delta1 table and do it again. If they do match,
  748.   go to the slow loop.
  749. }
  750. FAST:
  751.      i := i + Delta1[ord(Target[i])];
  752.      if (i <= Trglen) then goto FAST;
  753.  
  754.  
  755. {
  756.    If we have really skipped passed the end of Target, return.
  757. }
  758. if (i <= LARGE) then
  759.   begin
  760.   posDelta1F := 0;
  761.   Exit;
  762.   end;
  763.  
  764. {
  765.   Undo the large skip caused by the match in the fast loop.
  766. }
  767. i := (i - LARGE) - 1;
  768. oldi := i + 1;
  769. j := Patlen - 1;
  770.  
  771. SLOW:
  772.      if (j = 0) then
  773.        begin
  774.        posDelta1F := i + 1;
  775.        Exit;
  776.        end;
  777.  
  778.      if (Target[i] = Pattern[j]) then
  779.        begin
  780.        j := j - 1;
  781.        i := i - 1;
  782.        goto SLOW;
  783.        end;
  784.  
  785. {
  786.   A mismatch has been found. Skip by delta1, unless this is really a backward
  787.   skip. The skip is backward if the right end of Pattern is moved to a position
  788.   in Target to the left of its previous position in Target - i+skip < oldi.
  789.   Also, if the character in Target where the mismatch occured is the same
  790.   as the character on the right end of Pattern, then skip is a large number,
  791.   but really corresponds to sliding the Pattern backwards so its right end
  792.   lines up with the character in Target that caused the mismatch. In either
  793.   case we can move Pattern forward by 2 (of course we can always move forward
  794.   by at least 1, but it can be proved that the minimum skip is 2).
  795. }
  796. d1 := Delta1[ord(Target[i])];
  797. if (d1 <> LARGE) and (i + d1 > oldi) then
  798.   begin
  799.   i := i + d1;
  800.   end
  801. else
  802.   begin
  803.   i := oldi + 2;
  804.   end;
  805.  
  806. {
  807.   Check for skipping past end of Target. If not, then go to the fast loop.
  808.   Else Pattern is not in Target, return with a zero result.
  809. }
  810. if (i <= Trglen) then
  811.   begin
  812.   goto FAST;
  813.   end
  814. else
  815.   begin
  816.   posDelta1F := 0;
  817.   end;
  818.  
  819.  
  820.  
  821. end;  { function posDelta1F }
  822.  
  823.  
  824.  
  825.  
  826.  
  827. {------------------------------------------------------------------------------
  828.   Search an array of strings for a pattern, using the Boyer-Moore method
  829.   with just the delta1 table. This version uses the FAST-SLOW loops.
  830. -------------------------------------------------------------------------------
  831.  
  832.     ENTRY:
  833.           TargetList is an array of strings to search.
  834.           NumLines is the number of elements in Target.
  835.           Pattern is the pattern to search for.
  836.     EXIT:
  837.          Numfound is the number of lines in file TargetName that
  838.          contain Pattern.
  839.  
  840. ------------------------------------------------------------------------------}
  841.  
  842. procedure DoDelta1F(var NumFound: Integer) ;
  843.  
  844.  
  845. var
  846.   Delta1   : Del1Array; { The delta 1 table }
  847.   patlen   : Integer;   { Length of Pattern }
  848.   i        : Integer;   { Loop counter }
  849.   PD1F     : Integer;
  850.  
  851. begin {procedure DoDelta1F}
  852.  
  853. {
  854.   Create the delta1 table. Replace the entry corresponding to the right
  855.   of Pattern with a value larger than pattern length + target length.
  856.   This is used to detect a match in the fast loop.
  857. }
  858. MakeDelta1(Pattern, Delta1);
  859. patlen := length(Pattern);
  860. Delta1[ord(Pattern[patlen])] := LARGE;
  861.  
  862.  
  863.  
  864. for TimesCount := 1 to TOTTIMES do
  865.   begin
  866.  
  867.  
  868.   for i := 1 to NumLines do
  869.     begin
  870.     PD1F := posDelta1F(TargetList[i], Pattern, Delta1);
  871.     If PD1F > 0 then
  872.       Begin
  873.       NumFound := NumFound + 1;
  874.       end;
  875.     end; { for i }
  876.  
  877.  
  878.   end; { for TimesCount }
  879.  
  880. end; {procedure DoDelta1F}
  881.  
  882.  
  883. {------------------------------------------------------------------------------
  884.    Compare 2 substrings of Pattern.
  885. -------------------------------------------------------------------------------
  886.  
  887.     ENTRY:
  888.            Pattern is the pattern
  889.  
  890.            s1 is the starting position of the first substring.
  891.            The first substring extends from character s1 of Pattern to
  892.            the end of pattern.
  893.  
  894.            s2 is the starting position of the 2nd substring.
  895.            The 2nd substring starts at s2 and is as long as the first
  896.            substring.
  897.  
  898.     EXIT:
  899.          A boolean result. True if the 2 substrings match, False otherwise.
  900.  
  901.     NOTE:
  902.          This function is used to compare 2 substrings of a search pattern
  903.          as part of the process of creating a delta 2 table for the Boyer-
  904.          Moore string searching algorithm.
  905.  
  906.          The 1st substring extends from postion s1 of Pattern to the end of
  907.          Pattern. The 2nd substring may be anywhere to the left of the 1st
  908.          one. The 2nd substring may even "fall off" the left end of Pattern.
  909.          Characters which fall off the left end are ignored (ie. considered
  910.          to match). Thus the 2 substrings are compared starting from their
  911.          rightmost characters and comparing up to the point where the 2nd
  912.          substring falls off the left end of Pattern.
  913.  
  914. ------------------------------------------------------------------------------}
  915.  
  916. function cmpsubs(Pattern : Line; s1, s2: Integer): Boolean;
  917.  
  918. var
  919.    Patlen   : Integer; { Length of Pattern }
  920.    sublen   : Integer; { Length of 1st substring }
  921.    i        : Integer; { Right end of 1st substring }
  922.    j        : Integer; { Right end of 2nd substring }
  923.    count    : Integer; { Counter for number of characters to compare }
  924.  
  925. begin { function cmpsubs }
  926.  
  927. Patlen := Length(Pattern);
  928. sublen := Patlen - (s1 - 1);
  929.  
  930.  
  931. i := patlen;
  932.  
  933.  
  934. {
  935.   Define j, the right end of the 2nd substring.
  936.   If the 2nd substring doesn't fall off the left end of the pattern,
  937.   then j is simply sublen characters to the right of s2. If it does
  938.   fall off the left end, then use the rightmost characters of the 2nd
  939.   substring that don't fall off the left end. If the entire 2nd substring
  940.   falls off the left, then return TRUE.
  941. }
  942. if (s2 >= 1) then
  943.   begin
  944.   j := (s2 - 1) + sublen;
  945.   end
  946. else
  947.   begin
  948.   sublen := sublen + (s2 - 1);
  949.   if (sublen = 0) then
  950.     begin
  951.     cmpsubs := TRUE;
  952.     EXIT;
  953.     end;
  954.   j := sublen;
  955.   end;
  956.  
  957. {
  958.   Compare the 2 substrings.
  959. }
  960. count := 0;
  961. while (count < sublen) do
  962.   begin
  963.   if (Pattern[j] = Pattern[i]) then
  964.     begin
  965.     i := i - 1;
  966.     j := j - 1;
  967.     count := count + 1;
  968.     end
  969.   else
  970.     begin
  971.     cmpsubs := FALSE;
  972.     EXIT;
  973.     end;
  974.   end; { while count < sublen }
  975.  
  976. {
  977.   If we have made it this far then the substrings match.
  978.   Now we check the character to the immediate left of each substring.
  979.   If they are not equal we have a match and return TRUE. If they are
  980.   equal we don't have a match and return FALSE.
  981.  
  982.   We must also take into account the situation where the character to the
  983.   left of the 2nd substring falls off the left end of Pattern. This always
  984.   matches.
  985. }
  986. if (s2 > 1) then
  987.   begin
  988.   if (Pattern[j] = Pattern[i]) then
  989.     begin
  990.     cmpsubs := FALSE;
  991.     end
  992.   else
  993.     begin
  994.     cmpsubs := TRUE;
  995.     end;
  996.   end
  997. else
  998.   begin
  999.   cmpsubs := TRUE;
  1000.   end;
  1001.  
  1002. end;  { function cmpsubs }
  1003.  
  1004.  
  1005. {------------------------------------------------------------------------------
  1006.   Create a delta2 table using brute force methods.
  1007. -------------------------------------------------------------------------------
  1008.  
  1009.     ENTRY:
  1010.           Pattern is the pattern for which the table is contructed.
  1011.  
  1012.  
  1013.     EXIT:
  1014.          Delta2 is the delta 2 table.
  1015.  
  1016. ------------------------------------------------------------------------------}
  1017.  
  1018. procedure MakeDelta2BF(Pattern: Line; var Delta2: Del2Array);
  1019.  
  1020.  
  1021. var
  1022.    Patlen  : Integer; { The length of Pattern  }
  1023.    i       : Integer; { Position in Pattern for which delta2 value is computed }
  1024.    j       : Integer; { Position in Pattern to check for a match with
  1025.                         terminating substring starting at position i+1. }
  1026.  
  1027.  
  1028. begin { procedure MakeDelta2BF }
  1029.  
  1030. Patlen := Length(Pattern);
  1031.  
  1032.  
  1033. {
  1034.   Determine delta 2 value for character i of Pattern.
  1035. }
  1036. for i := 1 to Patlen do
  1037.   begin
  1038.   {
  1039.     Compare substring at position i+1 with substring of same length
  1040.     starting at position j. If no match is found, decrement j and
  1041.     try again. Repeat until a match is found. A match is guaranteed
  1042.     because the compare defines characters of the 2nd substring which
  1043.     fall off the left end of Pattern to match.
  1044.   }
  1045.   j := i;
  1046.   while (not cmpsubs(Pattern,i+1,j)) do
  1047.     begin
  1048.     j := j - 1;
  1049.     end; { while not cmpsubs }
  1050.   Delta2[i] := Patlen + 1 - j;
  1051.   end; { for i }
  1052.  
  1053. end;  { procedure MakeDelta2BF }
  1054.  
  1055.  
  1056.  
  1057. {------------------------------------------------------------------------------
  1058. Procedure   MakeDelta2WR
  1059. Create a delta 2 table using the Rytter correction to Knuth's algorithm.
  1060. -------------------------------------------------------------------------------
  1061.  
  1062.     ENTRY:
  1063.           Pattern is the pattern for which the table is contructed.
  1064.  
  1065.     EXIT:
  1066.          Delta2 is the delta 2 table.
  1067.  
  1068.     NOTE: See the following reference for an explanation of this method
  1069.           of constructing the delta 2 table.
  1070.  
  1071. ------------------------------------------------------------------------------}
  1072.  
  1073. procedure MakeDelta2WR(Pattern: Line; var Delta2: Del2Array);
  1074.  
  1075. var
  1076.    k       : Integer;
  1077.    patlen  : Integer; { Length of Pattern. }
  1078.    patlen2 : Integer; { Twice the length of Pattern. }
  1079.    j       : Integer;
  1080.    t       : Integer;
  1081.    f       : Array[1..MAXSTR] of Integer;
  1082.    f1      : Array[1..MAXSTR] of Integer;
  1083.    q       : Integer;
  1084.    q1      : Integer;
  1085.    j1      : Integer;
  1086.    t1      : Integer;
  1087.  
  1088.  
  1089. begin { procedure MakeDelta2WR }
  1090.  
  1091. patlen := Length(pattern);
  1092.  
  1093. {
  1094.   Section labeled A1 in Rytter's paper.
  1095. }
  1096. patlen2 := 2*patlen;
  1097. for K := 1 to patlen do
  1098.   begin
  1099.   Delta2[k] := patlen2 - k;
  1100.   end;
  1101.  
  1102. {
  1103.   Section labeled A2 in Rytter's paper.
  1104. }
  1105. j := patlen;
  1106. t := patlen + 1;
  1107. while (j > 0) do
  1108.   begin
  1109.   f[j] := t;
  1110.   while (t <= patlen) and (Pattern[j] <> Pattern[t]) do
  1111.     begin
  1112.     if (patlen - j < Delta2[t]) then Delta2[t] := patlen - j;
  1113.     t := f[t];
  1114.     end; { while (t <= patlen) and (Pattern[j] <> Pattern[t]) }
  1115.   t := t - 1;
  1116.   j := j - 1;
  1117.   end; { while (j > 0) }
  1118.  
  1119.  
  1120. q := t;
  1121. t := patlen + 1 - q;
  1122. q1 := 1;
  1123.  
  1124.  
  1125. {
  1126.   Section labeled B1 in Rytter's paper.
  1127. }
  1128. j1 := 1;
  1129. t1 := 0;
  1130. while (j1 <= t) do
  1131.   begin
  1132.   f1[j1] := t1;
  1133.   while (t1 >= 1) and (Pattern[j1] <> Pattern[t1]) do
  1134.     begin
  1135.     t1 := f1[t1];
  1136.     end; { while (t1 >= 1) and (Pattern[j1] <> Pattern[t1]) }
  1137.   t1 := t1 + 1;
  1138.   j1 := j1 + 1;
  1139.   end; { while (j1 <= t) }
  1140.  
  1141. {
  1142.   Section labeled B2 in Rytter's paper,
  1143. }
  1144. while (q < patlen) do
  1145.   begin
  1146.   for k := q1 to q do
  1147.     begin
  1148.     if (patlen + q -k < Delta2[k]) then Delta2[k] := patlen + q - k;
  1149.     end; { for k }
  1150.   q1 := q + 1;
  1151.   q := q + t - f1[t];
  1152.   end; { while (q < patlen) }
  1153.  
  1154.  
  1155. end;  { procedure MakeDelta2WR }
  1156.  
  1157.  
  1158.  
  1159. {------------------------------------------------------------------------------
  1160. Procedure   posDelta2F
  1161. Boyer-Moore with delta1 and delta2 tables.
  1162. -------------------------------------------------------------------------------
  1163.  
  1164.     ENTRY:
  1165.            Target is the text string to be searched
  1166.  
  1167.            Pattern is the string to search for
  1168.  
  1169.            Del1Array is the Boyer-Moore delta1 table
  1170.  
  1171.            Del2Array is the Boyer-Moore delta2 tabel
  1172.  
  1173.  
  1174.       Target, Pattern and Delta1 are declared as call by reference (var) to
  1175.       avoid having them copied on each call to PosDelta2.
  1176.  
  1177.  
  1178.     EXIT:
  1179.       An integer return value which indicates the starting position of Pattern
  1180.       in Target. It is zero if Pattern is not found.
  1181.  
  1182. ------------------------------------------------------------------------------}
  1183.  
  1184. function posDelta2F(var Target, Pattern : Line; var Delta1 : Del1Array;
  1185.                     var Delta2 : Del2Array): Integer;
  1186.  
  1187.  
  1188.  
  1189. label
  1190.   FAST, SLOW;               { Top of fast loop, top of slow loop }
  1191.  
  1192. var
  1193.   Trglen   : Integer; { The length of Target }
  1194.   Patlen   : Integer; { The length of Pattern }
  1195.   i, j     : Integer; { Pointers to Target and Pattern  }
  1196.   d1       : Integer; { Delta1 value when  mismatch occurrs in slow loop. }
  1197.  
  1198. begin { function posDelta2F }
  1199.  
  1200. Trglen := Length(Target);
  1201. Patlen := Length(Pattern);
  1202. if (Patlen > Trglen) then
  1203.   begin
  1204.   posDelta2F := 0;
  1205.   Exit;
  1206.   end;
  1207.  
  1208. i := Patlen;
  1209.  
  1210.  
  1211. {
  1212.   The fast loop. This loop compares the right end of Pattern to the
  1213.   corresponding character in Target. If they don't match, we skip
  1214.   according to the delta1 table and do it again. If they do match,
  1215.   go to the slow loop.
  1216. }
  1217. FAST:
  1218.      i := i + Delta1[ord(Target[i])];
  1219.      if (i <= Trglen) then goto FAST;
  1220.  
  1221.  
  1222. {
  1223.    If we have really skipped passed the end of Target, return.
  1224. }
  1225. if (i <= LARGE) then
  1226.   begin
  1227.   posDelta2F := 0;
  1228.   Exit;
  1229.   end;
  1230.  
  1231. {
  1232.   Undo the large skip caused by the match in the fast loop.
  1233. }
  1234. i := (i - LARGE) - 1;
  1235. j := Patlen - 1;
  1236.  
  1237. SLOW:
  1238.      if (j = 0) then
  1239.        begin
  1240.        posDelta2F := i + 1;
  1241.        Exit;
  1242.        end;
  1243.  
  1244.      if (Target[i] = Pattern[j]) then
  1245.        begin
  1246.        j := j - 1;
  1247.        i := i - 1;
  1248.        goto SLOW;
  1249.        end;
  1250.  
  1251. {
  1252.   A mismatch has been found.
  1253.   The delta1 value must be adjusted when it is "large". In this case
  1254.   the character in Target which caused the mismatch is the same as the
  1255.   character at the right end of Pattern. The usual delta1 value for this
  1256.   character is 0.
  1257.  
  1258.   Then choose the larger of the delta1 and delta2 values.
  1259. }
  1260. d1 := Delta1[ord(Target[i])];
  1261. if (d1 = LARGE) then d1 := 0;
  1262.  
  1263. if (d1 > Delta2[j]) then
  1264.   begin
  1265.   i := i + d1
  1266.   end
  1267. else
  1268.   begin
  1269.   i := i + Delta2[j];
  1270.   end;
  1271.  
  1272. {
  1273.   Check for skipping past end of Target. If not, then go to the fast loop.
  1274.   Else Pattern is not in Target, return with a zero result.
  1275. }
  1276. if (i <= Trglen) then
  1277.   begin
  1278.   goto FAST;
  1279.   end
  1280. else
  1281.   begin
  1282.   posDelta2F := 0;
  1283.   end;
  1284.  
  1285.  
  1286. end;  { function posDelta2F }
  1287.  
  1288.  
  1289.  
  1290. {------------------------------------------------------------------------------
  1291. Procedure   ShowDelta2
  1292.     Display the pattern with the delta2 values underneath it.
  1293. -------------------------------------------------------------------------------
  1294.  
  1295.     ENTRY:
  1296.           Pattern is the pattern being searched for.
  1297.  
  1298.           Delta2 is the Boyer-Moore delta2 table for Pattern.
  1299.  
  1300.     EXIT:
  1301.          The pattern is displayed with the delta2 value for each
  1302.          character of pattern displayed underneath the character:
  1303.  
  1304.            A  B  C  X  X  X  A  B  C
  1305.           14 13 12 11 10  9 11 10  1
  1306.  
  1307.  
  1308. ------------------------------------------------------------------------------}
  1309.  
  1310. procedure ShowDelta2(Pattern : Line; Delta2 : Del2Array);
  1311.  
  1312. var
  1313.   patlen  : Integer; { The length of Pattern. }
  1314.   i       : Integer;
  1315.  
  1316.  
  1317. begin { procedure ShowDelta2 }
  1318.  
  1319. patlen := Length(Pattern);
  1320.  
  1321. for i := 1 to  patlen do
  1322.   begin
  1323.   Write('  ', Pattern[i]);
  1324.   end;
  1325. WriteLn;
  1326. for i := 1 to  patlen do
  1327.   begin
  1328.   Write(' ', Delta2[i]:2);
  1329.   end;
  1330. WriteLn;
  1331.  
  1332. end;  { procedure ShowDelta2 }
  1333.  
  1334.  
  1335. {------------------------------------------------------------------------------
  1336.   Search an array of strings for a pattern, using the Boyer-Moore method
  1337.   with both the delta1 and delta2 tables.
  1338.   This version uses the FAST-SLOW loops.
  1339. -------------------------------------------------------------------------------
  1340.  
  1341.     ENTRY:
  1342.           TargetList is an array of strings to search.
  1343.           NumLines is the number of elements in Target.
  1344.           Pattern is the pattern to search for.
  1345.     EXIT:
  1346.          Numfound is the number of lines in file TargetName that
  1347.          contain Pattern.
  1348.  
  1349. ------------------------------------------------------------------------------}
  1350.  
  1351. procedure DoDelta2F(var NumFound: Integer) ;
  1352.  
  1353.  
  1354. VAR
  1355.   DELTA1   : DEL1ARRAY; { THE DELTA 1 TABLE }
  1356.   DELTA2   : DEL2ARRAY; { THE DELTA 2 TABLE }
  1357.   patlen   : Integer;   { Length of Pattern }
  1358.   i        : Integer;   { Loop counter }
  1359.   PD2      : Integer;
  1360.  
  1361. begin {procedure DoDelta2F}
  1362.  
  1363. {
  1364.   Create the delta1 table. Replace the entry corresponding to the right
  1365.   of Pattern with a value larger than pattern length + target length.
  1366.   This is used to detect a match in the fast loop.
  1367. }
  1368. MakeDelta1(Pattern, Delta1);
  1369. patlen := length(Pattern);
  1370. Delta1[ord(Pattern[patlen])] := LARGE;
  1371.  
  1372. {
  1373.   Create the Delta2 table.
  1374. }
  1375. MakeDelta2WR(Pattern, Delta2);
  1376.  
  1377.  
  1378.  
  1379. for TimesCount := 1 to TOTTIMES do
  1380.   begin
  1381.  
  1382.  
  1383.   for i := 1 to NumLines do
  1384.     begin
  1385.     PD2 := posDelta2F(TargetList[i], Pattern, Delta1, Delta2);
  1386.     If PD2 > 0 then
  1387.       Begin
  1388.       NumFound := NumFound + 1;
  1389.       end;
  1390.     end; { for i }
  1391.  
  1392.  
  1393.   end; { for TimesCount }
  1394.  
  1395. end; {procedure DoDelta2F}
  1396.  
  1397.  
  1398.  
  1399. {------------------------------------------------------------------------------
  1400.   Search a string for a pattern using the Boyer-Moore method with just delta1
  1401.   Use the FAST-SLOW loop structure. Count delta1 and other skips
  1402.   -------------------------------------------------------------------------------
  1403.  
  1404.     ENTRY:
  1405.       Target  is the string to search within.
  1406.       Pattern is the string to search for.
  1407.       Delta1  is the Boyer-Moore delta1 table. It contains the distance
  1408.               of each extended ASCII character from the right end of Pattern.
  1409.               Characters that do not appear in Pattern have distance Patlen.
  1410.  
  1411.       Target, Pattern and Delta1 are declared as call by reference (var) to
  1412.       avoid having them copied on each call to PosDelta1.
  1413.  
  1414.  
  1415.     EXIT:
  1416.       An integer return value which indicates the starting position of Pattern
  1417.       in Target. It is zero if Pattern is not found.
  1418.  
  1419.       D1Count is the number of times the Delta1 table was used.
  1420.  
  1421.       D2Count is the number of times a fixed skip of 2 was used.
  1422.  
  1423.       Skip1Count is the sum of the delta1 skips used.
  1424.  
  1425.       Skip2Count is the sum of the fixed skips used.
  1426.  
  1427. ------------------------------------------------------------------------------}
  1428.  
  1429. function posDelta1C(var Target, Pattern: Line; var Delta1: Del1Array;
  1430.                     var D1Count,  D2Count, Skip1Count, Skip2Count: Real):
  1431.                     Integer;
  1432.  
  1433. label
  1434.   FAST, SLOW;               { Top of fast loop, top of slow loop }
  1435.  
  1436. var
  1437.   Trglen   : Integer; { The length of Target }
  1438.   Patlen   : Integer; { The length of Pattern }
  1439.   i, j     : Integer; { Pointers to Target and Pattern  }
  1440.   oldi     : Integer; { Character of Target compared with right end of
  1441.                         Pattern. Used if Delta1 would cause a backward
  1442.                         shift. }
  1443.   d1       : Integer; { Delta1 value when  mismatch occurrs in slow loop. }
  1444.  
  1445.  
  1446. begin { function posDelta1C }
  1447.  
  1448. Trglen := Length(Target);
  1449. Patlen := Length(Pattern);
  1450. if (Patlen > Trglen) then
  1451.   begin
  1452.   posDelta1C := 0;
  1453.   Exit;
  1454.   end;
  1455.  
  1456. i := Patlen;
  1457.  
  1458.  
  1459. {
  1460.   The fast loop. This loop compares the right end of Pattern to the
  1461.   corresponding character in Target. If they don't match, we skip
  1462.   according to the delta1 table and do it again. If they do match,
  1463.   go to the slow loop.
  1464.   Also, count increment the delta1 counters. Note that because
  1465.   we are at the right end of Pattern the pattern shift is the
  1466.   same as the delta1 value.
  1467. }
  1468. FAST:
  1469.      D1Count := D1Count + 1;
  1470.      Skip1Count := Skip1Count + Delta1[ord(Target[i])];
  1471.      i := i + Delta1[ord(Target[i])];
  1472.      if (i <= Trglen) then goto FAST;
  1473.  
  1474.  
  1475. {
  1476.    If we have really skipped passed the end of Target, return.
  1477. }
  1478. if (i <= LARGE) then
  1479.   begin
  1480.   posDelta1C := 0;
  1481.   Exit;
  1482.   end;
  1483.  
  1484. {
  1485.   Undo the large skip caused by the match in the fast loop.
  1486. }
  1487. D1Count := D1Count - 1;
  1488. Skip1Count := Skip1Count - LARGE;
  1489. i := (i - LARGE) - 1;
  1490. oldi := i + 1;
  1491. j := Patlen - 1;
  1492.  
  1493.  
  1494. SLOW:
  1495.      if (j = 0) then
  1496.        begin
  1497.        posDelta1C := i + 1;
  1498.        Exit;
  1499.        end;
  1500.  
  1501.      if (Target[i] = Pattern[j]) then
  1502.        begin
  1503.        j := j - 1;
  1504.        i := i - 1;
  1505.        goto SLOW;
  1506.        end;
  1507.  
  1508. {
  1509.   A mismatch has been found. skip by delta1, unless this is really a backward
  1510.   skip. The skip is backward if the right end of Pattern is moved to a position
  1511.   in Target to the left of its previous position in Target - i+skip < oldi.
  1512.   Also, if the character in Target where the mismatch occured is the same
  1513.   as the character on the right end of Pattern, then skip is a large number,
  1514.   but really corresponds to sliding the Pattern backwards so its right end
  1515.   lines up with the character in Target that caused the mismatch. In either
  1516.   case we can move Pattern forward by 2 (of course we can always move forward
  1517.   by at least 1, but it can be proved that the minimum skip is 2).
  1518.  
  1519.   Also, increment the appropriate delta counters. If delta1 is used, the
  1520.   actual pattern shift depends on the delta1 value and the current position
  1521.   in Pattern. If the delta1 value is not used, the shift is fixed at 2.
  1522. }
  1523. d1 := Delta1[ord(Target[i])];
  1524. if (d1 <> LARGE) and (i + d1 > oldi) then
  1525.   begin
  1526.   D1Count := D1Count + 1;
  1527.   Skip1Count := Skip1Count + d1 - patlen + j;
  1528.   i := i + d1;
  1529.   end
  1530. else
  1531.   begin
  1532.   D2Count := D2Count + 1;
  1533.   Skip2Count := Skip2Count + 2;
  1534.   i := oldi + 2;
  1535.   end;
  1536.  
  1537. {
  1538.   Check for skipping past end of Target. If not, then go to the fast loop.
  1539.   Else Pattern is not in Target, return with a zero result.
  1540. }
  1541. if (i <= Trglen) then
  1542.   begin
  1543.   goto FAST;
  1544.   end
  1545. else
  1546.   begin
  1547.   posDelta1C := 0;
  1548.   end;
  1549.  
  1550.  
  1551.  
  1552. end;  { function posDelta1C }
  1553.  
  1554.  
  1555.  
  1556. {------------------------------------------------------------------------------
  1557.   Search an array of strings for a pattern, using the Boyer-Moore method
  1558.   with just the delta1 table. This version uses the FAST-SLOW loops.
  1559.   Count the number of delta1 and other skips.
  1560. -------------------------------------------------------------------------------
  1561.  
  1562.     ENTRY:
  1563.           TargetList is an array of strings to search.
  1564.           NumLines is the number of elements in Target.
  1565.           Pattern is the pattern to search for.
  1566.     EXIT:
  1567.          Numfound is the number of lines in file TargetName that
  1568.          contain Pattern.
  1569.  
  1570.          D1Count is the number of times the Delta1 table was used.
  1571.  
  1572.          Skip1Count is the sum of the delta1 skips used.
  1573.  
  1574.          D2Count is the number of times a fixed skip of 2 was used.
  1575.  
  1576.          Skip2Count is the sum of the fixed skips used.
  1577.  
  1578. ------------------------------------------------------------------------------}
  1579.  
  1580. procedure DoDelta1C(var NumFound: Integer;
  1581.                     var D1Count, D2Count, Skip1Count, Skip2Count: Real);
  1582.  
  1583. var
  1584.   Delta1   : Del1Array; { The delta 1 table }
  1585.   patlen   : Integer;   { Length of Pattern }
  1586.   i        : Integer;   { Loop counter }
  1587.   PD1C     : Integer;
  1588.  
  1589. begin {procedure DoDelta1C}
  1590.  
  1591. {
  1592.   Create the delta1 table. Replace the entry corresponding to the right
  1593.   of Pattern with a value larger than pattern length + target length.
  1594.   This is used to detect a match in the fast loop.
  1595. }
  1596. MakeDelta1(Pattern, Delta1);
  1597. patlen := length(Pattern);
  1598. Delta1[ord(Pattern[patlen])] := LARGE;
  1599.  
  1600. for i := 1 to NumLines do
  1601.   begin
  1602.   PD1C := posDelta1C(TargetList[i], Pattern, Delta1, D1Count, D2count,
  1603.                      Skip1Count, Skip2Count);
  1604.   If PD1C > 0 then
  1605.     Begin
  1606.     NumFound := NumFound + 1;
  1607.     end;
  1608.   end; { for i }
  1609.  
  1610. end; {procedure DoDelta1C}
  1611.  
  1612.  
  1613.  
  1614. {------------------------------------------------------------------------------
  1615. Procedure   posDelta2C
  1616. Boyer-Moore with delta1 and delta2 tables. Count delta1 and delta2 skips.
  1617. -------------------------------------------------------------------------------
  1618.  
  1619.     ENTRY:
  1620.            Target is the text string to be searched
  1621.  
  1622.            Pattern is the string to search for
  1623.  
  1624.            Del1Array is the Boyer-Moore delta1 table
  1625.  
  1626.            Del2Array is the Boyer-Moore delta2 tabel
  1627.  
  1628.  
  1629.       Target, Pattern and Delta1 are declared as call by reference (var) to
  1630.       avoid having them copied on each call to PosDelta2.
  1631.  
  1632.  
  1633.     EXIT:
  1634.       An integer return value which indicates the starting position of Pattern
  1635.       in Target. It is zero if Pattern is not found.
  1636.  
  1637.       D1Count is the number of times the Delta1 table was used.
  1638.  
  1639.       D2Count is the number of times a fixed skip of 2 was used.
  1640.  
  1641.       Skip1Count is the sum of the delta1 skips used.
  1642.  
  1643.       Skip2Count is the sum of the fixed skips used.
  1644.  
  1645. ------------------------------------------------------------------------------}
  1646.  
  1647. function posDelta2C(var Target, Pattern: Line; var Delta1: Del1Array;
  1648.                     var Delta2: Del2Array;
  1649.                     var D1Count,  D2Count, Skip1Count, Skip2Count: Real):
  1650.                     Integer;
  1651.  
  1652.  
  1653.  
  1654.  
  1655. label
  1656.   FAST, SLOW;               { Top of fast loop, top of slow loop }
  1657.  
  1658. var
  1659.   Trglen   : Integer; { The length of Target }
  1660.   Patlen   : Integer; { The length of Pattern }
  1661.   i, j     : Integer; { Pointers to Target and Pattern  }
  1662.   d1       : Integer; { Delta1 value when  mismatch occurrs in slow loop. }
  1663.  
  1664. begin { function posDelta2C }
  1665.  
  1666. Trglen := Length(Target);
  1667. Patlen := Length(Pattern);
  1668. if (Patlen > Trglen) then
  1669.   begin
  1670.   posDelta2C := 0;
  1671.   Exit;
  1672.   end;
  1673.  
  1674. i := Patlen;
  1675.  
  1676.  
  1677. {
  1678.   The fast loop. This loop compares the right end of Pattern to the
  1679.   corresponding character in Target. If they don't match, we skip
  1680.   according to the delta1 table and do it again. If they do match,
  1681.   go to the slow loop.
  1682. }
  1683. FAST:
  1684.      D1Count := D1Count + 1;
  1685.      Skip1Count := Skip1Count + Delta1[ord(Target[i])];
  1686.      i := i + Delta1[ord(Target[i])];
  1687.      if (i <= Trglen) then goto FAST;
  1688.  
  1689.  
  1690. {
  1691.    If we have really skipped passed the end of Target, return.
  1692. }
  1693. if (i <= LARGE) then
  1694.   begin
  1695.   posDelta2C := 0;
  1696.   Exit;
  1697.   end;
  1698.  
  1699. {
  1700.   Undo the large skip caused by the match in the fast loop.
  1701. }
  1702. D1Count := D1Count - 1;
  1703. Skip1Count := Skip1Count - LARGE;
  1704. i := (i - LARGE) - 1;
  1705. j := Patlen - 1;
  1706.  
  1707. SLOW:
  1708.      if (j = 0) then
  1709.        begin
  1710.        posDelta2C := i + 1;
  1711.        Exit;
  1712.        end;
  1713.  
  1714.      if (Target[i] = Pattern[j]) then
  1715.        begin
  1716.        j := j - 1;
  1717.        i := i - 1;
  1718.        goto SLOW;
  1719.        end;
  1720.  
  1721. {
  1722.   A mismatch has been found.
  1723.   The delta1 value must be adjusted when it is "large". In this case
  1724.   the character in Target which caused the mismatch is the same as the
  1725.   character at the right end of Pattern. The usual delta1 value for this
  1726.   character is 0.
  1727.  
  1728.   Then choose the larger of the delta1 and delta2 values.
  1729. }
  1730. d1 := Delta1[ord(Target[i])];
  1731. if (d1 = LARGE) then d1 := 0;
  1732.  
  1733. if (d1 > Delta2[j]) then
  1734.   begin
  1735.   D1Count := D1Count + 1;
  1736.   Skip1Count := Skip1Count + d1 - patlen + j;
  1737.   i := i + d1
  1738.   end
  1739. else
  1740.   begin
  1741.   D2Count := D2Count + 1;
  1742.   Skip2Count := Skip2Count + Delta2[j] - patlen + j;
  1743.   i := i + Delta2[j];
  1744.   end;
  1745.  
  1746. {
  1747.   Check for skipping past end of Target. If not, then go to the fast loop.
  1748.   Else Pattern is not in Target, return with a zero result.
  1749. }
  1750. if (i <= Trglen) then
  1751.   begin
  1752.   goto FAST;
  1753.   end
  1754. else
  1755.   begin
  1756.   posDelta2C := 0;
  1757.   end;
  1758.  
  1759.  
  1760. end;  { function posDelta2C }
  1761.  
  1762.  
  1763.  
  1764.  
  1765. {------------------------------------------------------------------------------
  1766.   Search an array of strings for a pattern, using the Boyer-Moore method
  1767.   with the delta1 and delta2 tables. This version uses the FAST-SLOW loops.
  1768.   Count the number of delta1 and delta2 skips.
  1769. -------------------------------------------------------------------------------
  1770.  
  1771.     ENTRY:
  1772.           TargetList is an array of strings to search.
  1773.           NumLines is the number of elements in Target.
  1774.           Pattern is the pattern to search for.
  1775.     EXIT:
  1776.          Numfound is the number of lines in file TargetName that
  1777.          contain Pattern.
  1778.  
  1779.          D1Count is the number of times the Delta1 table was used.
  1780.  
  1781.          Skip1Count is the sum of the delta1 skips used.
  1782.  
  1783.          D2Count is the number of times a fixed skip of 2 was used.
  1784.  
  1785.          Skip2Count is the sum of the fixed skips used.
  1786.  
  1787. ------------------------------------------------------------------------------}
  1788.  
  1789. procedure DoDelta2C(var NumFound: Integer;
  1790.                     var D1Count, D2Count, Skip1Count, Skip2Count: Real);
  1791.  
  1792. var
  1793.   Delta1   : Del1Array; { The delta 1 table }
  1794.   Delta2   : Del2Array; { The delta 2 table }
  1795.   patlen   : Integer;   { Length of Pattern }
  1796.   i        : Integer;   { Loop counter }
  1797.   PD2C     : Integer;
  1798.  
  1799. begin {procedure DoDelta2C}
  1800.  
  1801. {
  1802.   Create the delta1 table. Replace the entry corresponding to the right
  1803.   of Pattern with a value larger than pattern length + target length.
  1804.   This is used to detect a match in the fast loop.
  1805. }
  1806. MakeDelta1(Pattern, Delta1);
  1807. patlen := length(Pattern);
  1808. Delta1[ord(Pattern[patlen])] := LARGE;
  1809.  
  1810. {
  1811.   Create the Delta2 table.
  1812. }
  1813. MakeDelta2WR(Pattern, Delta2);
  1814. ShowDelta2(Pattern, Delta2);
  1815.  
  1816. for i := 1 to NumLines do
  1817.   begin
  1818.   PD2C := posDelta2C(TargetList[i], Pattern, Delta1, Delta2, D1Count, D2count,
  1819.                      Skip1Count, Skip2Count);
  1820.   If PD2C > 0 then
  1821.     Begin
  1822.     NumFound := NumFound + 1;
  1823.     end;
  1824.   end; { for i }
  1825.  
  1826.  
  1827. end; {procedure DoDelta2C}
  1828.  
  1829.  
  1830.  
  1831. {------------------------------------------------------------------------------
  1832.   Create the delta2 table several times using the brute force method
  1833. -------------------------------------------------------------------------------
  1834.  
  1835. ------------------------------------------------------------------------------}
  1836.  
  1837. procedure DoMakD2BF;
  1838.  
  1839. var
  1840.   Delta2   : Del2Array; { The delta 2 table }
  1841.  
  1842. begin {procedure DoMakD2BF}
  1843.  
  1844.  
  1845. for TimesCount := 1 to TOTTIMES do
  1846.   begin
  1847.  
  1848.   MakeDelta2BF(Pattern, Delta2);
  1849.  
  1850.   end; { for TimesCount }
  1851.  
  1852. ShowDelta2(Pattern, Delta2);
  1853.  
  1854. end; {procedure DoMakD2BF}
  1855.  
  1856.  
  1857.  
  1858.  
  1859.  
  1860.  
  1861. {------------------------------------------------------------------------------
  1862.   Create the delta2 table several times using the Knuth/Rytter method
  1863. -------------------------------------------------------------------------------
  1864.  
  1865. ------------------------------------------------------------------------------}
  1866.  
  1867. procedure DoMakD2WR;
  1868.  
  1869. var
  1870.   Delta2   : Del2Array; { The delta 2 table }
  1871.  
  1872. begin {procedure DoMakD2WR}
  1873.  
  1874.  
  1875. for TimesCount := 1 to TOTTIMES do
  1876.   begin
  1877.  
  1878.   MakeDelta2WR(Pattern, Delta2);
  1879.  
  1880.   end; { for TimesCount }
  1881.  
  1882.  
  1883. ShowDelta2(Pattern, Delta2);
  1884.  
  1885.  
  1886. end; {procedure DoMakD2WR}
  1887.  
  1888.  
  1889.  
  1890.  
  1891. {------------------------------------------------------------------------------
  1892.   Setup for file search: Get starting time and open target file.
  1893. -------------------------------------------------------------------------------
  1894.  
  1895.     ENTRY:
  1896.           TargetName is the file to search.
  1897.  
  1898.     EXIT:
  1899.          Begtime contains the starting time for the search. It is in
  1900.          seconds since the start of the day.
  1901.  
  1902.          TargetVar is a file variable for TargetName. TargetName has been
  1903.          opened for sequential input.
  1904.  
  1905.          TargetLines is an array of strings which holds the contents
  1906.          of the file TargetName.
  1907.  
  1908.          NumFound, the number of lines in TargetName which contain Pattern,
  1909.          is initialized to zero.
  1910.  
  1911. ------------------------------------------------------------------------------}
  1912.  
  1913. procedure Setup ;
  1914.  
  1915. var
  1916.    OK  : Boolean;
  1917.  
  1918. begin {procedure Setup}
  1919.  
  1920.  
  1921.  
  1922. {
  1923.   Open file to be searched for input.
  1924. }
  1925. Assign(TargetVar, TargetName);
  1926. {$I-}
  1927. Reset(TargetVar);
  1928. {$I+}
  1929. OK := (IOresult = 0);
  1930. if (not OK) then
  1931.   begin
  1932.   WriteLn('Target file ', TargetName, ' not found.');
  1933.   Halt;
  1934.   end;
  1935.  
  1936. {
  1937.   Read the file and store its contents in an array of strings.
  1938. }
  1939. NumLines := 0;
  1940. while (not Eof(TargetVar)) and (NumLines < MAXLINE) do
  1941.   begin
  1942.   NumLines := NumLines + 1;
  1943.   ReadLn(TargetVar, TargetList[NumLines]);
  1944.   end;
  1945.  
  1946. Close(TargetVar);
  1947.  
  1948.  
  1949. NumFound := 0;
  1950.  
  1951. D1Count    := 0;
  1952. D2Count    := 0;
  1953. Skip1Count := 0;
  1954. Skip2Count := 0;
  1955.  
  1956. {
  1957.   Initialize elapsed timer.
  1958. }
  1959. BegTime := GetTime;
  1960.  
  1961. end; {procedure Setup}
  1962.  
  1963.  
  1964.  
  1965. {------------------------------------------------------------------------------
  1966.   Cleanup after the search: Close file. Compute time for search.
  1967. -------------------------------------------------------------------------------
  1968.  
  1969.     ENTRY:
  1970.           TargetVar is the file variable for the file just searched.
  1971.  
  1972.           Begtime is the starting time for the search in seconds since
  1973.           the beginning of the day.
  1974.  
  1975.           NumFound is the number of lines in the target file containing Pattern.
  1976.  
  1977.     EXIT:
  1978.          The target file is closed.
  1979.          The elapsed time for the search, in seconds, is displayed on the
  1980.          standard output device.
  1981.  
  1982. ------------------------------------------------------------------------------}
  1983.  
  1984. procedure Cleanup;
  1985.  
  1986. var
  1987.   TotTime    : Real;    { Total time for the search, in seconds }
  1988.  
  1989. begin {procedure Cleanup}
  1990.  
  1991. Close(TargetVar);
  1992.  
  1993. {
  1994.   Elapsed time is in seconds.
  1995. }
  1996. TotTime := GetTime - BegTime;
  1997. Write(' Alg: ', Algorithm, ' File: ', TargetName, ' Pattern: /', Pattern,
  1998.         '/ Time: ', TotTime:5:1, ' Lines: ', NumFound);
  1999.  
  2000. {
  2001.   Write counts if counting versions of the algorithms were used.
  2002. }
  2003. if (Algorithm = 4) or (Algorithm = 5) then
  2004.   begin
  2005.   Write(' D1: ', D1Count:6:0, ' Skip1: ', Skip1Count:6:0, ' D2: ', D2Count:6:0,
  2006.         ' Skip2: ', Skip2Count:6:0);
  2007.   end;
  2008.  
  2009. WriteLn;
  2010.  
  2011. end; {procedure Cleanup}
  2012.  
  2013.  
  2014.  
  2015. begin {program Search}
  2016.  
  2017.     {
  2018.       Arbitrarily large constant for FAST/SLOW loop versions.
  2019.       Subtract MAXSTR to prevent overflow.
  2020.     }
  2021.     LARGE := MAXINT - MAXSTR;
  2022.  
  2023.     GetProb(Algorithm, TargetName, Pattern);
  2024.  
  2025.     Setup;
  2026.  
  2027.  
  2028.     case Algorithm of
  2029.       0: DoBruteForce(NumFound);
  2030.       1: DoDelta1(NumFound);
  2031.       2: DoDelta1F(NumFound);
  2032.       3: DoDelta2F(NumFound);
  2033.       4: DoDelta1C(NumFound, D1Count, D2Count, Skip1Count, Skip2Count);
  2034.       5: DoDelta2C(NumFound, D1Count, D2Count, Skip1Count, Skip2Count);
  2035.       6: DoMakD2BF;
  2036.       7: DoMakD2WR;
  2037.     end; { case Algorithm }
  2038.  
  2039.  
  2040.  
  2041.  
  2042.  
  2043.     Cleanup;
  2044.  
  2045.  
  2046. end. {program Search}
  2047.  
  2048.  
  2049.  
  2050.