home *** CD-ROM | disk | FTP | other *** search
/ CD Actual Thematic 7: Programming / CDAT7.iso / Share / Editores / Perl5 / perl / lib / site / String / Approx.pm
Encoding:
Perl POD Document  |  1997-08-10  |  19.9 KB  |  873 lines

  1. package String::Approx;
  2.  
  3. =head1 NAME
  4.  
  5. String::Approx - match and substitute approximately (aka fuzzy matching)
  6.  
  7. =head1 SYNOPSIS
  8.  
  9.     use String::Approx qw(amatch asubstitute);
  10.  
  11. =head1 DESCRIPTION
  12.  
  13. B<Approximate> is defined here as I<k-differences>.  One I<difference>
  14. is an insertion, a deletion, or a substitution of one character.
  15. The I<k> in the I<k-differences> is the maximum number of differences.
  16.  
  17. For example I<1-difference> means that a match is found if there is
  18. one character too many (insertion) or one character missing (deletion)
  19. or one character changed (substitution).  Those are I<exclusive or>s:
  20. that is, I<not> one of each type of modification but I<exactly one>.
  21.  
  22. =head2 The default approximateness
  23.  
  24. The default approximateness is I<10 %> of the length of the
  25. approximate pattern or I<at least 1>: I<0-differences> being the exact
  26. matching which can be done very effectively using the usual Perl
  27. function C<index()> or normal regular expression matching.
  28.  
  29. =head2 amatch
  30.  
  31.     use String::Approx qw(amatch);
  32.  
  33.     amatch("PATTERN");
  34.     amatch("PATTERN", @LIST);
  35.     amatch("PATTERN", [ @MODS ]);
  36.     amatch("PATTERN", [ @MODS ], @LIST);
  37.  
  38. The PATTERN is B<a string>, not a regular expression.  The regular
  39. expression metanotation (C<. ? * + {...,...} ( ) | [ ] ^ $ \w ...>)
  40. will be understood as literal characters, that is, a C<*> means in
  41. regex terms C<\*>, not I<"match 0 or more times">.
  42.  
  43. The LIST is the list of strings to match against the pattern.
  44. If no LIST is given matches against C<$_>.
  45.  
  46. The MODS are the modifiers that tell how approximately to match.
  47. See below for more detailed explanation.
  48. B<NOTE>: The syntax really is C<[ @MODS ]>, the square
  49. brackets C<[ ]> must be in there.  See below for examples.
  50.  
  51. In scalar context C<amatch()> returns the number of successful
  52. matches.  In list context C<amatch()> returns the strings that
  53. had matches.
  54.  
  55. Example:
  56.  
  57.     use String::Approx qw(amatch);
  58.  
  59.     open(WORDS, '/usr/dict/words') or die;
  60.  
  61.     while (<WORDS>) {
  62.         print if amatch('perl');
  63.     }
  64.  
  65. or the same ignoring case:
  66.  
  67.     use String::Approx qw(amatch);
  68.  
  69.     open(WORDS, '/usr/dict/words') or die;
  70.  
  71.     while (<WORDS>) {
  72.         print if amatch('perl', ['i']);
  73.     }
  74.  
  75. =head2 asubstitute
  76.  
  77.     use String::Approx qw(asubstitute);
  78.  
  79.     asubstitute("PATTERN", "SUBSTITUTION");
  80.     asubstitute("PATTERN", "SUBSTITUTION", @LIST);
  81.     asubstitute("PATTERN", "SUBSTITUTION", [ @MODS ]);
  82.     asubstitute("PATTERN", "SUBSTITUTION", [ @MODS ], @LIST);
  83.  
  84. The PATTERN is B<a string>, not a regular expression.  The regular
  85. expression metanotation (C<. ? * + {...,...} ( ) | [ ] ^ $ \w ...>)
  86. will be understood as literal characters, that is, a C<*> means in
  87. regex terms C<\*>, not I<"match 0 or more times">.
  88.  
  89. Also the SUBSTITUTION is B<a string>, not a regular expression.  Well,
  90. mostly.  I<Most of the> regular expression metanotation (C<.>, C<?>,
  91. C<*>, C<+>, ...) will be not understood as literal characters, that
  92. is, a C<*> means in regex terms C<\*>, not I<"match 0 or more times">.
  93. The understood notations are
  94.  
  95. =over 8
  96.  
  97. =item    C<$`>
  98.  
  99. the part I<before> the approximate match
  100.  
  101. =item    C<$&>
  102.  
  103. the approximately matched part
  104.  
  105. =item    C<$'>
  106.  
  107. the part I<after> the approximate match
  108.  
  109. =back
  110.  
  111. The MODS are the modifiers that tell how approximately to match.
  112. See below for more detailed explanation.
  113. B<NOTE>: Yes, the syntax is really C<[ @MODS ]>, the square
  114. brackets C<[ ]> must be in there.  See below for examples.
  115.  
  116. The LIST is the list of strings to substitute against the pattern.
  117. If no LIST is given substitutes against C<$_>.
  118.  
  119. In scalar context C<asubstitute()> returns the number of successful
  120. substitutions.  In list context C<asubstitute()> returns the strings
  121. that had substitutions.
  122.  
  123. Examples:
  124.  
  125.     use String::Approx qw(asubstitute);
  126.  
  127.     open(WORDS, '/usr/dict/words') or die;
  128.     while (<WORDS>) {
  129.         print if asubstitute('perl', '($&)');
  130.     }
  131.  
  132. or the same ignoring case:
  133.  
  134.     use String::Approx qw(asubstitute);
  135.  
  136.     open(WORDS, '/usr/dict/words') or die;
  137.     while (<WORDS>) {
  138.         print if asubstitute('perl', '($&)', [ 'i' ]);
  139.     }
  140.  
  141. =head2 Modifiers
  142.  
  143. The MODS argument both in amatch() and asubstitute() is a list of
  144. strings that control the matching of PATTERN.  The first two, B<i> and
  145. B<g>, are the usual regular expression match/substitute modifiers, the
  146. rest are special for approximate matching/substitution.
  147.  
  148. =over 8
  149.  
  150. =item    i
  151.  
  152. Match/Substitute ignoring case, case-insensitively.
  153.  
  154. =item    g
  155.  
  156. Substitute I<globally>, that is, all the approximate matches, not just
  157. the first one.
  158.  
  159. =item    I<k>
  160.  
  161. The maximum number of differences.
  162. For example 2.
  163.  
  164. =item    II<k>
  165.  
  166. The maximum number of insertions.
  167. For example 'I2'.
  168.  
  169. =item    DI<k>
  170.  
  171. The maximum number of deletions.
  172. For example 'D2'.
  173.  
  174. =item    SI<k>
  175.  
  176. The maximum number of substitutions.
  177. For example 'S2'.
  178.  
  179. =item    I<k>%
  180.  
  181. The maximum relative number of differences.
  182. For example '10%'.
  183.  
  184. =item    II<k>%
  185.  
  186. The maximum relative number of insertions.
  187. For example 'I5%'.
  188.  
  189. =item    DI<k>%
  190.  
  191. The maximum relative number of deletions.
  192. For example 'D5%'.
  193.  
  194. =item    SI<k>%
  195.  
  196. The maximum relative number of substitutions.
  197. For example 'S5%'.
  198.  
  199. =back
  200.  
  201. I<The regular expression modifiers> C<o m s x> I<are> B<not supported>
  202. because their definitions for approximate matching are less than clear.
  203.  
  204. The relative number of differences is relative to the length of the
  205. PATTERN, rounded up: if, for example, the PATTERN is C<'bouillabaise'>
  206. and the MODS is C<['20%']> the I<k> becomes I<3>.
  207.  
  208. If you want to B<disable> a particular kind of difference you need
  209. to explicitly set it to zero: for example C<'D0'> allows no deletions.
  210.  
  211. In case of conflicting definitions the later ones silently override,
  212. for example:
  213.  
  214.     [2, 'I3', 'I1']
  215.  
  216. equals
  217.  
  218.     ['I1', 'D2', 'S2']
  219.  
  220. =head1 EXAMPLES
  221.  
  222. The following examples assume the following template:
  223.  
  224.     use String::Approx qw(amatch asubstitute);
  225.  
  226.     open(WORDS, "/usr/dict/words") or die;
  227.     while (<WORDS>) {
  228.         # <---
  229.     }
  230.  
  231. and the following examples just replace the above 'C<# E<lt>--->' line.
  232.  
  233. =head2 Matching from the C<$_>
  234.  
  235. =over 8
  236.  
  237. =item Match 'perl' with one difference
  238.  
  239.     print if amatch('perl');
  240.  
  241. The I<one difference> is automatically the result in this case because
  242. first the rule of the I<10 %> of the length of the pattern ('C<perl>')
  243. is used and then the I<at least 1> rule.
  244.  
  245. =item Match 'perl' with case ignored
  246.  
  247.     print if amatch('perl', [ 'i' ]);
  248.  
  249. The case is ignored in matching (C<i>).
  250.  
  251. =item Match 'perl' with one insertion
  252.  
  253.     print if amatch('perl', [ '0', 'I1' ]);
  254.  
  255. The I<one insertion> is easiest achieved with first disabling any
  256. approximateness (C<0>) and then enabling one insertion (C<I1>).
  257.  
  258. =item Match 'perl' with zero deletions
  259.  
  260.     print if amatch('perl', [ 'D0' ]);
  261.  
  262. The I<zero deletion> is easily achieved with simply disabling any
  263. deletions (C<D0>), the other types of differences, the insertions and
  264. substitutions, are still enabled.
  265.  
  266. =item Substitute 'perl' approximately with HTML emboldening
  267.  
  268.     print if amatch('perl', '<B>$&</B>', [ 'g' ]);
  269.  
  270. All (C<g>) of the approximately matching parts of the input are
  271. surrounded by the C<HTML> emboldening markup.
  272.  
  273. =back
  274.  
  275. =head2 Matching from a list
  276.  
  277. The above examples match against the default variable B<$_>.
  278. The rest of the examples show how the match from a list.
  279. The template is now:
  280.  
  281.     use String::Approx qw(amatch asubstitute);
  282.  
  283.     open(WORDS, "/usr/dict/words") or die;
  284.     @words = <words>;
  285.     # <---
  286.  
  287. and the examples still go where the 'C<# E<lt>--->' line is.
  288.  
  289. =over 8
  290.  
  291. =item Match 'perl' with one difference from a list
  292.  
  293.     @matched = amatch('perl', @words);
  294.  
  295. The C<@matched> contains the elements of the C<@words> that matched
  296. approximately.
  297.  
  298. =item Substitute 'perl' approximately with HTML emphasizing from a list
  299.  
  300.     @substituted = asubstitute('perl', '<EM>$&</EM>', [ 'g' ], @words);
  301.  
  302. The C<@substituted> contains B<with all> (C<g>) B<the substitutions>
  303. the elements of the C<@words> that matched approximately.
  304.  
  305. =back
  306.  
  307. =head1 ERROR MESSAGES
  308.  
  309. =over 8
  310.  
  311. =item amatch: $_ is undefined: what are you matching against?
  312.  
  313. =item asubstitute: $_ is undefined: what are you matching against?
  314.  
  315. These happen when you have nothing in C<$_> and try to C<amatch()> or
  316. C<asubstitute()>.  Perhaps you are using the Perl option C<-e> but you
  317. did forget the Perl option C<-n>?
  318.  
  319. =item amatch: too long pattern.
  320.  
  321. This happens when the pattern is too long for matching.
  322.  
  323. When matching long patterns, C<String::Approx> attempts to partition
  324. the match.  In other words, it tries to do the matching incrementally
  325. in smaller parts.
  326.  
  327. If this fails the above message is shown.  Please try using shorter
  328. match patterns.
  329.  
  330. See below for L<LIMITATIONS/Pattern length> for more detailed
  331. explanation why this happens.
  332.  
  333. =item asubstitute: too long pattern.
  334.  
  335. This happens when the pattern is too long for substituting.
  336.  
  337. The partitioning scheme explained above that is used for matching long
  338. patterns cannot, sadly enough, be used substituting.
  339.  
  340. Please try using shorter substitution patterns.
  341.  
  342. See below for L<LIMITATIONS/Pattern length> for more detailed
  343. explanation why this happens.
  344.  
  345. =back
  346.  
  347. =head1 VERSION
  348.  
  349. Version 2.1.
  350.  
  351. =head1 LIMITATIONS
  352.  
  353. =head2 Fixed pattern
  354.  
  355. The PATTERNs of C<amatch()> and C<asubstitute()> are fixed strings,
  356. they are not regular expressions.  The I<SUBSTITUTION> of
  357. C<asubstitute()> is a bit more flexible than that but not by much.
  358.  
  359. =head2 Pattern length
  360.  
  361. The approximate matching algorithm is B<very aggressive>.  In
  362. mathematical terms it is I<O(exp(n) * x**2)>. This means that
  363. when the pattern length and/or the approximateness grows the
  364. matching or substitution take much longer time and memory.
  365.  
  366. For C<amatch()> this can be avoided by I<partitioning> the pattern,
  367. matching it in shorter subpatterns.  This makes matching a bit slower
  368. and a bit more fuzzier, more approximate.  For C<asubstitute()> this
  369. partitioning cannot be done, the absolute maximum for the substitution
  370. pattern length is B<19> but sometimes, for example it the approximateness
  371. is increased, even shorter patterns are too much.  When this happens,
  372. you must use shorter patterns.
  373.  
  374. =head2 Speed
  375.  
  376. I<Despite the about 20-fold speed increase> from the C<String::Approx>
  377. I<version 1> B<agrep is still faster>.  If you do not know what
  378. C<agrep> is: it is a program like the UNIX grep but it knows, among
  379. other things, how to do approximate matching.  C<agrep> is still about
  380. 30 times faster than I<Perl> + C<String::Approx>.  B<NOTE>: all these
  381. speeds were measured in one particular system using one particular set
  382. of tests: your mileage will vary.
  383.  
  384. For long patterns, more than about B<40>, the first 
  385.  
  386. =head2 Incompatibilities with C<String::Approx> I<v1.*>
  387.  
  388. If you have been using regular expression modifiers (B<i>, B<g>) you
  389. lose.  Sorry about that.  The syntax simply is not compatible.  I had
  390. to choose between having C<amatch()> match and C<asubstitute()>
  391. substitute elsewhere than just in $_ I<and> the old messy way of
  392. having an unlimited number of modifiers.  The first need won.
  393.  
  394. B<There is a backward compability mode>, though, if you do not want to
  395. change your C<amatch()> and C<asubstitute()> calls.  You B<have> to
  396. change your C<use> line, however:
  397.  
  398.     use String::Approx qw(amatch compat1);
  399.  
  400. That is, you must add the C<compat1> symbol if you want to be
  401. compatible with the C<String::Approx> version 1 call syntax.
  402.  
  403. =head1 AUTHOR
  404.  
  405. Jarkko Hietaniemi C<E<lt>jhi@iki.fiE<gt>>
  406.  
  407. =head1 ACKNOWLEDGEMENTS
  408.  
  409. Nathan Torkington C<E<lt>gnat@frii.comE<gt>>
  410.  
  411. =cut
  412.  
  413. require 5;
  414.  
  415. use strict;
  416. $^W = 1;
  417.  
  418. use vars qw($PACKAGE $VERSION $compat1
  419.         @ISA @EXPORT_OK
  420.         %P @aL @dL @Pl %Pp);
  421.  
  422. $PACKAGE = 'String::Approx';
  423. $VERSION = '2.0';
  424.  
  425. $compat1 = 0;
  426.  
  427. require Exporter;
  428.  
  429. @ISA = qw(Exporter);
  430.  
  431. @EXPORT_OK = qw(amatch asubstitute);
  432.  
  433. # Catch the 'compat1' tag.
  434.  
  435. sub import {
  436.     my $this = shift;
  437.     my (@list, $sym);
  438.     for $sym (@_) { $sym eq 'compat1' ? $compat1 = 1 : push(@list, $sym) }
  439.     local $Exporter::ExportLevel = 1; 
  440.     Exporter::import($this, @list);
  441. }
  442.  
  443. sub _estimate {
  444.     my ($l, $m) = @_;
  445.     my $p = 5 ** ($m + 2);
  446.  
  447.     (3 * $p * $l ** 2 + (8 - $p) * $l - $p) / 8;
  448. }
  449.  
  450. sub _compile {
  451.     my ($pattern, $I, $D, $S) = @_;
  452. #    print STDERR "_compile(@_)\n";
  453.     my ($j, $p, %p, %q, $l, $k, $mxm);
  454.     my @p = ();
  455.  
  456.     $mxm = $I;
  457.     $mxm = $D if ($D > $mxm);
  458.     $mxm = $S if ($S > $mxm);
  459.  
  460.     $l = length($pattern);
  461.  
  462. #    print "mxm = $mxm, l = $l\n";
  463.  
  464.     # the estimated length of the resulting pattern must be less than 32767
  465.  
  466.     my $est = _estimate($l, $mxm);
  467.  
  468.     if ($est > 32767) {
  469.     my ($a, $b, $i);
  470.     my $mp;
  471.  
  472. #    print "est = $est\n";
  473.  
  474.     # compute and cache the partitions per length
  475.  
  476.     unless (defined $Pl[$l][$mxm]) {
  477.         my ($np, $sp, $fp, $gp);
  478.  
  479.         $np = int(log($l)) + 1;
  480.         $np = 2 if ($np < 2);
  481.         $sp = int($l / $np);
  482.         $fp = $l - $np * $sp;
  483.         $gp = $sp + $fp;
  484.         $mp = int($mxm / $np);
  485.         $mp = 1 if ($mp < 1);
  486.  
  487. #        print "  np = $np, sp = $sp, fp = $fp, gp = $gp, mp = $mp\n";
  488.  
  489.         $est = _estimate($gp, $mp);
  490.  
  491. #        print "  est = $est\n";
  492.  
  493.         while ($est > 32767) {
  494.         # same rule here as above about the length of the pattern.
  495.         $sp--;
  496.         $np = int($l / $sp);
  497.         $fp = $l - $np * $sp;
  498.         $gp = $sp + $fp;
  499.         $mp = int($mxm / $np);
  500.         $mp = 1 if ($mp < 1);
  501. #        print "    np = $np, sp = $sp, fp = $fp, gp = $gp, mp = $mp\n";
  502.         $est = _estimate($gp, $mp);
  503. #        print "  est = $est\n";
  504.         }
  505.  
  506.         ($a, $b) = (0, $sp + $fp);
  507.         push(@{$Pl[$l][$mxm]}, [$a, $b]);
  508.         $a += $fp;
  509.         $b  = $sp;
  510.         for ($i = 1; $i < $np; $i++) {
  511.         $a += $sp;
  512. #        print "a = $a, b = $b\n";
  513.         push(@{$Pl[$l][$mxm]}, [$a, $b]);
  514.         }
  515.     }
  516.  
  517.     my $pi = $I ? int($mp / $I + 0.9) : 0;
  518.     my $pd = $D ? int($mp / $D + 0.9) : 0;
  519.     my $ps = $S ? int($mp / $S + 0.9) : 0;
  520.  
  521.     # compute and cache the pattern partitions
  522.  
  523.     unless (defined $Pp{$pattern}[$mxm]) {
  524.         for $i (@{$Pl[$l][$mxm]}) {
  525.         push(@{$Pp{$pattern}[$mxm]},
  526.              [substr($pattern, $$i[0], $$i[1]), $pi, $pd, $ps]);
  527.         }
  528.     }
  529.  
  530.     @p = @{$Pp{$pattern}[$mxm]};
  531.     
  532.     } else {
  533.     push(@p, [$pattern, $I, $D, $S]);
  534.     }
  535.  
  536.     my $i0 = 1;        # The start index for the insertions.
  537.  
  538.     my $pp;        # The current partition.
  539.  
  540.     for $pp (@p) {    # The partition loop.
  541.  
  542.     %p = ();
  543.  
  544.     my ($i, $d, $s) = @$pp[1..4];    # The per-partition I, D, S.
  545.  
  546.     $pp = $$pp[0];            # The partition string itself.
  547.  
  548. #    print STDERR "$pp $i $d $s\n";
  549.  
  550.     $p{$pp} = length($pp);
  551.  
  552.     while ($i or $d or $s) {
  553.  
  554.         %q = ();
  555.     
  556.         # the insertions
  557.  
  558.         if ($i) {
  559.         $i--;
  560.         while (($p, $l) = each %p) {
  561.             my $lp1 = $l + 1;
  562.  
  563.             for ($j = $i0; $j < $l; $j++) {
  564.             $k = $p;
  565.             substr($k, $j) = '.' . substr($k, $j);
  566.             $q{$k} = $lp1;
  567.             }
  568.         }
  569.  
  570.         # After the first partition we want one insertion
  571.         # before every partition - at index 0.  $i0 was
  572.         # initialized before the partition loop as 1 and
  573.         # thus the first partition does not get the one insertion
  574.         # in front of it.
  575.  
  576.         $i0 = 0;
  577.         }
  578.  
  579.         # the deletions
  580.  
  581.         if ($d) {
  582.         $d--;
  583.         while (($p, $l) = each %p) {
  584.             if ($l) {
  585.             my $lm1 = $l - 1;
  586.  
  587.             for ($j = 0; $j < $l; $j++) {
  588.                 $k = $p;
  589.                 substr($k, $j) = substr($k, $j + 1);
  590.                 $q{$k} = $lm1;
  591.             }
  592.             }
  593.         }
  594.         }
  595.  
  596.         # the substitutions
  597.  
  598.         if ($s) {
  599.         $s--;
  600.         while (($p, $l) = each %p) {
  601.             for ($j = 0; $j <= $l; $j++) {
  602.             $k = $p;
  603.             substr($k, $j, 1) = '.';
  604.             $q{$k} = $l;
  605.             }
  606.         }
  607.         }
  608.  
  609.         while (($k, $l) = each %q) { $p{$k} = $l }
  610.     }
  611.  
  612.     # the pattern
  613.  
  614.     push(@{$P{$pattern}[$I][$D][$S]},
  615.          join('|', sort { length($b) <=> length($a) } keys %p));
  616.  
  617.     }
  618. }
  619.  
  620. sub _mods {
  621.     my ($mods, $aI, $aD, $aS, $rI, $rD, $rS) = @_;
  622.     my $remods = '';
  623.     my $mod;
  624.  
  625.     for $mod (@$mods) {
  626.     while ($mod ne '') {
  627.         if ($mod =~ s/^([IDS]?)(\d+)(%?)//) {
  628.         if ($1 ne '') {
  629.             if ($3 ne '') {
  630.             if    ($1 eq 'I') { $$rI = 0.01 * $2 }
  631.             elsif ($1 eq 'D') { $$rD = 0.01 * $2 }
  632.             else              { $$rS = 0.01 * $2 }
  633.             } else {
  634.             if    ($1 eq 'I') { $$aI = $2 }
  635.             elsif ($1 eq 'D') { $$aD = $2 }
  636.             else              { $$aS = $2 }
  637.             }
  638.         } else {
  639.             if ($3 ne '') {
  640.             $$rI = $$rD = $$rS = 0.01 * $2;
  641.             } else {
  642.             $$aI = $$aD = $$aS = $2;
  643.             }
  644.         }
  645.         } elsif ($compat1 and $mod =~ s/^([igmsxo])//) {
  646.         $remods .= $1;
  647.         } elsif ($mod =~ s/^([ig])//) {
  648.         $remods .= $1;
  649.         } else {
  650.         die $PACKAGE, ": unknown modifier '$mod'\n";
  651.         }
  652.     }
  653.     }
  654.  
  655.     $remods ne '' ? $remods : undef;
  656. }
  657.  
  658. sub _mids {
  659.     my ($len, $aI, $aD, $aS, $rI, $rD, $rS) = @_;
  660.  
  661.     my $r = int(0.1 * $len + 0.9);
  662.  
  663.     if    (    defined $rI) { $aI = int($rI * $len) }
  664.     elsif (not defined $aI) { $aI = $r }
  665.  
  666.     if    (    defined $rD) { $aD = int($rD * $len) }
  667.     elsif (not defined $aD) { $aD = $r }
  668.  
  669.     if    (    defined $rS) { $aS = int($rS * $len) }
  670.     elsif (not defined $aS) { $aS = $r }
  671.  
  672.     ($aI, $aD, $aS);
  673. }
  674.  
  675. sub amatch {
  676.     my ($pattern, @list) = @_;
  677.     my ($aI, $aD, $aS, $rI, $rD, $rS);
  678.  
  679.     my $len = length($pattern);
  680.  
  681.     my $remods;
  682.  
  683.     if ($compat1 or ref $list[0]) {
  684.     my $mods;
  685.  
  686.     if ($compat1) {
  687.         $mods = [ @list ];
  688.         @list = ();
  689.     } else {
  690.         $mods = shift(@list);
  691.     }
  692.  
  693.     $remods = _mods($mods, \$aI, \$aD, \$aS, \$rI, \$rD, \$rS);
  694.  
  695.     ($aI, $aD, $aS) = _mids($len, $aI, $aD, $aS, $rI, $rD, $rS);
  696.     } else {
  697.     $dL[$len] = int(0.1 * $len + 0.9) unless $dL[$len];
  698.     $aI = $aD = $aS = $dL[$len];
  699.     }
  700.  
  701.     die "amatch: \$_ is undefined: what are you matching against?\n"
  702.     if (not defined $_ and @list == 0);
  703.  
  704.     _compile($pattern, $aI, $aD, $aS)
  705.     unless ref $P{$pattern}[$aI][$aD][$aS];
  706.  
  707.     my @mpat = @{$P{$pattern}[$aI][$aD][$aS]};
  708.     my $mpat;
  709.  
  710.     # match against the @list
  711.  
  712.     if (@mpat == 1) {
  713.  
  714.     # the simple non-partitioned match
  715.  
  716.     $mpat = $mpat[0];
  717.  
  718.     $mpat = '(?' . $remods . ')' . $mpat if defined $remods;
  719.  
  720. #    print STDERR "mpat = $mpat\n";
  721.  
  722.     if (@list) {
  723.  
  724.         # match against the @list
  725.  
  726.         my @m = eval { grep /$mpat/, @list };
  727.         die "amatch: too long pattern.\n"
  728.         if ($@ =~ /regexp too big/);
  729.         return @m;
  730.     }
  731.  
  732.     # match against the $_
  733.  
  734.     my $m;
  735.  
  736.     eval { $m = /$mpat/ };
  737.     die "amatch: too long pattern.\n"
  738.         if ($@ =~ /regexp too big/);
  739.     return ($_) if $m;
  740.  
  741.     } else {
  742.  
  743.     # the partitioned match
  744.  
  745.     if (@list) {
  746.  
  747.         # match against the @list
  748.  
  749.         my @pos = ();
  750.         my @bad = ();
  751.         my ($i, $bad);
  752.  
  753.         for $mpat (@mpat) {
  754.         if (@pos) {
  755.             for $i (@list) {
  756.             pos($i) = shift(@pos);
  757.             }
  758.         } else {
  759.             @pos = ();
  760.         }
  761.         for ($i = $bad = 0; $i < @list; $i++) {
  762.             unless ($bad[$i]) {
  763.             if (eval { $list[$i] =~ /$mpat/g }) {
  764.                 die "amatch: too long pattern.\n"
  765.                 if ($@ =~ /regexp too big/);
  766.                 $pos[$i] = pos($list[$i]);
  767.             } else {
  768.                 $bad[$i] = $bad++;
  769.                 return () if $bad == @list;
  770.             }
  771.             }
  772.         }
  773.         }
  774.         
  775.         my @got = ();
  776.  
  777.         for ($i = 0; $i < @list; $i++) {
  778.         push(@got) unless $bad[$i];
  779.         }
  780.  
  781.         return @got;
  782.     }
  783.     
  784.     # match against the $_
  785.  
  786.     while ($mpat = shift(@mpat)) {
  787.         return () unless eval { /$mpat/g };
  788.         die "amatch: too long pattern.\n"
  789.         if ($@ =~ /regexp too big/);
  790.         return ($_) if (@mpat == 0);
  791.     }
  792.     }
  793.  
  794.     return ();
  795. }
  796.  
  797. sub _subst {
  798.     my ($sub, $pre, $match, $post) = @_;
  799.  
  800.     $sub =~ s/\$`/$pre/g;
  801.     $sub =~ s/\$&/$match/g;
  802.     $sub =~ s/\$'/$post/g;
  803.  
  804.     $sub;
  805. }
  806.  
  807. sub asubstitute {
  808.     my ($pattern, $sub, @list) = @_;
  809.     my ($aI, $aD, $aS, $rI, $rD, $rS);
  810.  
  811.     my $len = length($pattern);
  812.  
  813.     my $remods;
  814.  
  815.     if ($compat1 or ref $list[0]) {
  816.     my $mods;
  817.  
  818.     if ($compat1) {
  819.         $mods = [ @list ];
  820.         @list = ();
  821.     } else {
  822.         $mods = shift(@list);
  823.     }
  824.  
  825.     $remods = _mods($mods, \$aI, \$aD, \$aS, \$rI, \$rD, \$rS);
  826.  
  827.     ($aI, $aD, $aS) = _mids($len, $aI, $aD, $aS, $rI, $rD, $rS);
  828.     } else {
  829.     $dL[$len] = $len < 11 ? 1 : int(0.1 * $len) unless $dL[$len];
  830.     $aI = $aD = $aS = $dL[$len];
  831.     }
  832.  
  833.     die "asubstitute: \$_ is undefined: what are you matching against?\n"
  834.     if (not defined $_ and @list == 0);
  835.  
  836.     _compile($pattern, $aI, $aD, $aS)
  837.     unless defined $P{$pattern}[$aI][$aD][$aS];
  838.  
  839.     my @spat = @{$P{$pattern}[$aI][$aD][$aS]};
  840.     my $spat = $spat[0];
  841.     
  842.     $spat = '(?' . $remods . ')' . $spat if defined $remods;
  843.  
  844.     if (@list) {
  845.     my (@m, $sm, $s);
  846.  
  847.     for $sm (@list) {
  848.         eval { $s = $sm =~ s/($spat)/_subst($sub, $`, $1, $')/e };
  849.         die "asubstitute: too long pattern, maximum pattern length 19.\n"
  850.         if ($@ =~ /regexp too big/);
  851.         push(@m, $sm) if ($s);
  852.     }
  853.  
  854.     return @m;
  855.     }
  856.  
  857.     die "asubstitute: \$_ is undefined: what are you matching against?\n"
  858.     unless defined $_;
  859.  
  860.     my $s;
  861.  
  862.     eval { $s = s/($spat)/_subst($sub, $`, $1, $')/e };
  863.     die "asubstitute: too long pattern, maximum pattern length 19.\n"
  864.     if ($@ =~ /regexp too big/);
  865.     return ($_) if $s;
  866.  
  867.     ();
  868. }
  869.  
  870. 1;
  871.  
  872. # eof
  873.