home *** CD-ROM | disk | FTP | other *** search
/ PC-X 1997 October / pcx14_9710.iso / swag / strings.swg / 0133_String Matching Routines.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-08-30  |  15.0 KB  |  492 lines

  1. {WookieWare Home Defense Series cautiously presents
  2.  String matching routines, Public Domain effective immediately.
  3.  Please bestow credit in any distributed software or source.
  4.  (Yes, they're tested. No, I don't claim to have written them in half
  5.   an hour.)                                             }
  6.  
  7.  
  8. Uses crt,dos;
  9. const seeds:string='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  10. var string1,string2:string;
  11.     i:integer;
  12.     percent,percent2,percent3:integer;
  13.  
  14.  
  15.  
  16. function similar100(st1,st2:string):word;
  17. {This one started the whole thing. Loosely based on an algorithm called
  18.  SIMILAR.ASM  written by John W. Ratcliff and David E. Metzener
  19.  only a lot more understandable. Returns percentage match. Pretty slow
  20.  compared to the ASM versions. Case sensitive.
  21.  Ron Nossaman Sept. 30 1994 }
  22. var score:integer;
  23.  
  24.    procedure compare(s1,s2:string);
  25.    var s1l,s1r,s2l,s2r,looker:integer;
  26.    begin
  27.       s1l:=1;s2l:=1;
  28.       s1r:=length(s1);
  29.       s2r:=length(s2);
  30.       looker:=s2l;
  31.    {increment s1, sweep s2}
  32.       repeat
  33.          if s1[s1l]=s2[looker] then
  34.          begin             {got a match}
  35.             inc(s1l);      {next position on everything}
  36.             inc(looker);
  37.             s2l:=looker;   {pull up starting position marker}
  38.             inc(score);
  39.          end else inc(looker); {no match, continue sweep}
  40.          if looker>s2r then    {looker swept past end of string}
  41.          begin
  42.             looker:=s2l;     {restore looker to last unmatched position}
  43.             if s2l>s2r then s1l:=s1r;
  44.             inc(s1l);        {next char in first string for matching}
  45.          end;
  46.       until s1l>s1r;
  47.    end;
  48. begin
  49.    score:=0;
  50.    compare(st1,st2);
  51.    compare(st2,st1);
  52.    score:=(score*100)div(length(st1)+length(st2));
  53.    similar100:=score;
  54. end;
  55.  
  56.  
  57.  
  58.  
  59. {$F+} {I don't know for sure, might be necessary in multi segment program}
  60.  
  61. Function Match(Var s1:String; Var s2:String):word;
  62. {Uncle Ron's algorithm to compare two strings, returns percentage match}
  63. {Case sensitive}
  64. {Ron Nossaman Oct2, 1994}
  65. begin
  66.    asm
  67.       LES DI,[S2]
  68.       LDS SI,[S1]
  69.       Xor dx,dx        {zero score}
  70.       xor ax,ax
  71.       cmp [si],al       {is byte1 a zero?}
  72.       je @strerr       {yes, BAIL}
  73.       cmp [di],al
  74.       jne @docmp
  75. @strerr:
  76.       jmp @millertime        {BAIL}
  77.       { ;neither strings zero length, do it}
  78. @docmp:
  79.       cld
  80.       Xor ax,ax
  81.       mov al,[di]       {get length S2}
  82.       mov cx,ax         {save in cx}
  83.       add ax,di
  84.       mov bx,ax         {bx=pointer last byte S2}
  85.       inc di            {di=pointer first byte S2}
  86.       Xor ax,ax
  87.       mov al,[si]
  88.       push ax
  89.       add ax,cx
  90.       mov cx,ax         {total length both strings}
  91.       pop ax
  92.       add ax,si         {ax=pointer last byte S1}
  93.       inc si            {si=pointer first byte S1}
  94.       {ax=lastchar s1}
  95.       {bx=lastchar s2}
  96.       {si=firstchar s1}
  97.       {di=firstchar s2}
  98.  
  99.       push cx           {save 'total' characters}
  100.       push bx           {save s2 end}
  101.       push ax           {save s1 end}
  102.  
  103.       mov cx,0          {indicator of first pass through compare}
  104.       jmp @compare
  105. @round2:
  106.       LES DI,[S1]     {swap string beginnings}
  107.       LDS SI,[S2]
  108.       inc si
  109.       inc di
  110.       pop bx          {s2 end swapped}
  111.       pop ax          {s1 end swapped}
  112.                       {'total' still on stack}
  113.       mov cx,1          {pass 2 indicator}
  114.  
  115. @compare:
  116.       push cx     {save pass indicator}
  117.       mov cx,di   {let keeper remember starting point}
  118. @workloop:
  119.       push ax       {save eos pointers to free up registers}
  120.       push bx
  121.       xor ax,ax
  122.       mov al,[si]
  123.       mov bx,ax
  124.       mov al,[di]
  125.       cmp ax,bx     {are chars equal?}
  126.       jne @nomatch  {no, pass on}
  127.       inc si        {yes, increment both string position pointers}
  128.       inc di
  129.       mov cx,di     {keeper remembers new starting position}
  130.       inc dx        {score}
  131.       jmp @progress
  132. @nomatch:
  133.       inc di    {no match, try next char in second string}
  134. @progress:
  135.       pop bx        {restore end of string pointers}
  136.       pop ax
  137.       cmp di,bx     {is string 2 used up without match?}
  138.       jle @nofix    {nope, go on}
  139.       mov di,cx     {restore last unmatched position}
  140.       cmp di,bx     {is string2 matched to the end?}
  141.       jle @nofix2   {no, go try next letter of string1}
  142.       mov si,ax     {yes, nothing left to compare, cancel further search}
  143. @nofix2:
  144.       inc si        {next char string1}
  145. @nofix:
  146.       cmp si,ax     {done yet?}
  147.       jle @workloop {nope, hiho}
  148.       pop cx        {retreive pass indicator}
  149.       cmp cx,0      {0=pass1}
  150.       je @round2    {go back for pass 2}
  151.       mov ax,dx     {score}
  152.       mov cx,100
  153.       mul cx
  154.       pop cx      {get 'total' characters}
  155.       div cx
  156. @millertime:
  157.       mov @result,ax
  158.    end;
  159. end;
  160.  
  161.  
  162.  
  163. Function Match2(Var s1:String; Var s2:String):word;
  164. {Uncle Ron's algorithm to compare two strings, returns percentage match}
  165. {a tad smaller, faster. Still Case sensitive}
  166. {Ron Nossaman Oct 4, 1994}
  167. begin
  168.    asm
  169.       les di,[s2]
  170.       lds si,[s1]
  171.       xor ax,ax
  172.       mov al,[si]
  173.       cmp al,0
  174.       je @nolength
  175.       mov cx,ax        {cx= length of string1}
  176.       mov al,[di]
  177.       cmp al,0
  178.       jne @docmp       {ax= length of string2}
  179. @nolength:
  180.       jmp @millertime        {BAIL}
  181.  
  182. @docmp:       { ;neither strings zero length, do it}
  183.       cld
  184.       mov dx,ax         {save length(s2)}
  185.       add ax,di
  186.       mov bx,ax         {bx= pointer last char s2}
  187.       inc di            {di= pointer first char s2}
  188.       mov ax,dx         {retreive length(s2)}
  189.       add ax,cx         {+length(s1)}
  190.       push ax           {save total length both strings until final scoring}
  191.       mov ax,cx         {length(s1)}
  192.       add ax,si         {ax=pointer last char s1}
  193.       inc si            {si=pointer first char s1}
  194.       Xor dx,dx         {zero score}
  195.  
  196.  
  197.       {cast:}           {ax=lastchar s1}
  198.                         {bx=lastchar s2}
  199.                         {si=firstchar s1}
  200.                         {di=firstchar s2}
  201.                         {dx=accumulated score}
  202.                         {cx=temporary position marker during compare}
  203.  
  204.  
  205.       mov cx,0          {indicator of first pass through compare}
  206.       jmp @compare
  207. @round2:
  208.       les di,[s1]     {swap string beginnings}
  209.       lds si,[s2]
  210.       inc si
  211.       inc di
  212.       xchg ax,bx      {swap s1 and s2 end pointers}
  213.                       {'total' still on stack}
  214.       mov cx,1          {pass 2 indicator}
  215.  
  216. @compare:
  217.       push cx     {save pass indicator}
  218.       mov cx,di   {let keeper remember starting point}
  219. @workloop:
  220.       push ax       {save eos pointer to free up ax register}
  221.       mov al,[si]
  222.       mov ah,al
  223.       mov al,[di]
  224.       cmp al,ah     {are chars equal?}
  225.       jne @nomatch  {no, pass on}
  226.       inc si        {yes, increment both string position pointers}
  227.       inc di
  228.       mov cx,di     {keeper remembers new starting position}
  229.       inc dx        {score}
  230.       jmp @progress
  231. @nomatch:
  232.       inc di    {no match, try next char in second string}
  233. @progress:
  234.       pop ax       {restore end of string pointer}
  235.       cmp di,bx     {is string 2 used up without match?}
  236.       jle @nofix    {nope, go on}
  237.       mov di,cx     {restore last unmatched position}
  238.       cmp di,bx     {is string2 matched to the end?}
  239.       jle @nofix2   {no, go try next letter of string1}
  240.       mov si,ax     {yes, nothing left to compare, cancel further search}
  241. @nofix2:
  242.       inc si        {next char string1}
  243. @nofix:
  244.       cmp si,ax     {done yet?}
  245.       jle @workloop {nope, hiho}
  246.       pop cx        {retreive pass indicator}
  247.       cmp cx,0      {0=pass1}
  248.       je @round2    {go back for pass 2}
  249.       mov ax,dx     {score}
  250.       mov cx,100
  251.       mul cx
  252.       pop cx      {get 'total' characters}
  253.       div cx
  254. @millertime:
  255.       mov @result,ax
  256.    end;
  257. end;
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264. Function Match3(Var s1:String; Var s2:String; case_sensitive:boolean):word;
  265. {Uncle Ron's algorithm to compare two strings, returns percentage match}
  266. {Case sensitive/not switch    Most versatile, speed comparison varies}
  267. {Ron Nossaman Oct 29, 1994}
  268. begin
  269.    asm
  270.       push ds
  271.       les di,[s2]
  272.       lds si,[s1]
  273.       xor ax,ax
  274.       SEGDS mov al,[si]
  275.       cmp al,0
  276.       je @nolength
  277.       mov cx,ax        {cx= length of string1}
  278.       SEGES mov al,[di]
  279.       cmp al,0
  280.       jne @docmp       {ax= length of string2}
  281. @nolength:
  282.       jmp @millertime        {BAIL}
  283.  
  284. @docmp:       { ;neither strings zero length, do it}
  285.       cld
  286.       mov dx,ax         {save length(s2)}
  287.       add ax,di
  288.       mov bx,ax         {bx= pointer last char s2}
  289.       inc di            {di= pointer first char s2}
  290.       mov ax,dx         {retreive length(s2)}
  291.       add ax,cx         {+length(s1)}
  292.       push ax           {save total length both strings until final scoring}
  293.       mov ax,cx         {length(s1)}
  294.       add ax,si         {ax=pointer last char s1}
  295.       inc si            {si=pointer first char s1}
  296.       Xor dx,dx         {zero score}
  297.  
  298.  
  299.       {cast:}           {ax=lastchar s1}
  300.                         {bx=lastchar s2}
  301.                         {si=firstchar s1}
  302.                         {di=firstchar s2}
  303.                         {dx=accumulated score}
  304.                         {cx=temporary position marker during compare}
  305.  
  306.  
  307.       mov cx,0          {indicator flag of first pass through compare}
  308.                    {cheap dodge, since you can't call & ret in T.P. asm}
  309.       jmp @compare
  310. @round2:
  311.       les di,[s1]     {swap string beginnings}
  312.       lds si,[s2]
  313.       inc si
  314.       inc di
  315.       xchg ax,bx      {swap s1 and s2 end pointers}
  316.                       {'total' still on stack}
  317.       mov cx,1          {pass 2 indicator}
  318.  
  319. @compare:
  320.       push cx     {save pass indicator}
  321.       mov cx,di   {let keeper remember starting point}
  322. @workloop:
  323.       push ax       {save eos pointer to free up ax register}
  324.       SEGDS mov al,[si]
  325.       cmp case_sensitive,0
  326.       jnz @CaseOK1
  327.       cmp al,'Z'
  328.       jg  @CaseOK1
  329.       cmp al,'A'
  330.       jl  @CaseOK1
  331.       or al,$20
  332. @CaseOK1:
  333.       mov ah,al
  334.       SEGES mov al,[di]
  335.       cmp case_sensitive,0
  336.       jnz @CaseOK2
  337.       cmp al,'Z'
  338.       jg  @CaseOK2
  339.       cmp al,'A'
  340.       jl  @CaseOK2
  341.       or al,$20
  342. @CaseOK2:
  343.       cmp al,ah     {are chars equal?}
  344.       jne @nomatch  {no, pass on}
  345.       inc si        {yes, increment both string position pointers}
  346.       inc di
  347.       mov cx,di     {keeper remembers new starting position}
  348.       inc dx        {score}
  349.       jmp @progress
  350. @nomatch:
  351.       inc di    {no match, try next char in second string}
  352. @progress:
  353.       pop ax       {restore end of string pointer}
  354.       cmp di,bx     {is string 2 used up without match?}
  355.       jle @nofix    {nope, go on}
  356.       mov di,cx     {restore last unmatched position}
  357.       cmp di,bx     {is string 2 matched to the end?}
  358.       jle @nofix2   {no, go try next letter of string1}
  359.       mov si,ax     {yes, nothing left to compare, cancel further search}
  360. @nofix2:
  361.       inc si        {next char string1}
  362. @nofix:
  363.       cmp si,ax     {done yet?}
  364.       jle @workloop {nope, hiho}
  365.       pop cx        {retreive pass indicator}
  366.       cmp cx,0      {0=pass1}
  367.       je @round2    {go back for pass 2}
  368.       mov ax,dx     {score}
  369.       mov cx,100
  370.       mul cx
  371.       pop cx      {get 'total' characters}
  372.       div cx
  373. @millertime:
  374.       mov @result,ax
  375.       pop ds
  376.    end;
  377. end;
  378.  
  379.  
  380.  
  381.  
  382. function bickell2(s1,s2:string):integer; {not quite, but similar}
  383. const
  384.    weight:array[ord('a')..ord('{')]of byte=(
  385.         3,6,5,4,3,5,5,4,3,8,7,4,5,3,3,5,7,4,3,3,4,6,5,8,8,9,0);
  386.      (* a b c d e f g h i j k l m n o p q r s t u v w x y z { *)
  387. var sort1,sort2:string;
  388.     i,bick1,bick2:integer;
  389.     b1,b2:array[ord('a')..ord('{')]of byte;
  390.  
  391. begin
  392.    sort1:=s1; sort2:=s2;
  393.    for i:=1 to length(sort1) do if (sort1[i]<'a')or(sort1[i]>'z') then
  394.    begin
  395.       case sort1[i] of
  396.        'A'..'Z':sort1[i]:=char(ord(sort1[i])or 32);
  397.         else sort1[i]:='{';
  398.       end;
  399.    end;
  400.    for i:=1 to length(sort2) do if (sort2[i]<'a')or(sort2[i]>'z') then
  401.    begin
  402.       case sort2[i] of
  403.        'A'..'Z':sort2[i]:=char(ord(sort2[i])or 32);
  404.         else sort2[i]:='{';
  405.       end;
  406.    end;
  407.    fillchar(b1,sizeof(b1),0);
  408.    fillchar(b2,sizeof(b2),0);
  409.  
  410.   { weed out duplicates, sort}
  411.    for i:=1 to length(sort1) do b1[ord(sort1[i])]:=weight[ord(sort1[i])];
  412.    for i:=1 to length(sort2) do b2[ord(sort2[i])]:=weight[ord(sort2[i])];
  413.  
  414.   {get total for comparison}
  415.    bick1:=0;
  416.    for i:=ord('a') to ord('{') do bick1:=bick1+b1[i]+b2[i];
  417.  
  418.   {add up all letters common to both words}
  419.    bick2:=0;
  420.    for i:=ord('a') to ord('{') do if b1[i]<>0 then
  421.      if (b1[i]=b2[i]) then bick2:=bick2+b1[i]+b2[i];
  422.  
  423.   {figure match}
  424.    bickell2:=(bick2*100)div bick1;
  425. end;
  426.  
  427.  
  428.  
  429. procedure timer;
  430. var i:integer;
  431.     oldpercent,percent:integer;
  432.     h1,m1,s1,hund1,h2,m2,s2,hund2,h3,m3,s3,hund3:Word;
  433.     strt,stp:real;
  434. begin
  435.    string1:='ThanKyo';
  436.    string2:='tHanKyouR';
  437.    write('Timing "Similar100" ');
  438.    GetTime(h1,m1,s1,hund1);
  439.    for i:=1 to 30000 do percent:=similar100(string1,string2);
  440.    gettime(h2,m2,s2,hund2);
  441.    strt:=(h1*3600)+(m1*60)+s1+(hund1/100);
  442.    stp:=(h2*3600)+(m2*60)+s2+(hund2/100);
  443.    WriteLn(percent,'%  Elapsed time ',(stp-strt):0:2,' seconds');
  444.    write('Timing "Match"      ');
  445.    GetTime(h1,m1,s1,hund1);
  446.    for i:=1 to 30000 do percent:=match(string1,string2);
  447.    gettime(h2,m2,s2,hund2);
  448.    strt:=(h1*3600)+(m1*60)+s1+(hund1/100);
  449.    stp:=(h2*3600)+(m2*60)+s2+(hund2/100);
  450.    WriteLn(percent,'%  Elapsed time ',(stp-strt):0:2,' seconds');
  451.    write('Timing "Match2"     ');
  452.    gettime(h1,m1,s1,hund1);
  453.    for i:=1 to 30000 do percent:=match2(string1,string2);
  454.    gettime(h2,m2,s2,hund2);
  455.    strt:=(h1*3600)+(m1*60)+s1+(hund1/100);
  456.    stp:=(h2*3600)+(m2*60)+s2+(hund2/100);
  457.    WriteLn(percent,'%  Elapsed time ',(stp-strt):0:2,' seconds');
  458.    delay(100);
  459.    write('Timing "Match3"     ');
  460.    GetTime(h1,m1,s1,hund1);
  461.    for i:=1 to 30000 do percent:=match3(string1,string2,false);
  462.    gettime(h2,m2,s2,hund2);
  463.    strt:=(h1*3600)+(m1*60)+s1+(hund1/100);
  464.    stp:=(h2*3600)+(m2*60)+s2+(hund2/100);
  465.    WriteLn(percent,'%  Elapsed time ',(stp-strt):0:2,' seconds');
  466.    delay(100);
  467.    write('Timing "Bickell"     ');
  468.    GetTime(h1,m1,s1,hund1);
  469.    for i:=1 to 30000 do percent:=bickell2(string1,string2);
  470.    gettime(h2,m2,s2,hund2);
  471.    strt:=(h1*3600)+(m1*60)+s1+(hund1/100);
  472.    stp:=(h2*3600)+(m2*60)+s2+(hund2/100);
  473.    WriteLn(percent,'%  Elapsed time ',(stp-strt):0:2,' seconds');
  474. end;
  475. {$F-}
  476.  
  477. Begin
  478.    clrscr;
  479.  
  480.   repeat
  481.      string1:='';
  482.      for i:=1 to random(10)+2 do string1:=string1+copy(seeds,random(52)+1,1);
  483.      string2:='';
  484.      for i:=1 to random(10)+2 do string2:=string2+copy(seeds,random(52)+1,1);
  485.       percent:=bickell2(String1,String2);
  486.       percent2:=match3(string1,string2,false);
  487.       if (percent>50)or(percent2>50)
  488.          then writeln(percent,' ',percent2,'  ', string1,'  ',string2);
  489.   until keypressed;
  490.  
  491.   for i:=1 to 3 do timer;
  492. end.