File Coverage

blib/lib/Text/Fuzzy.pm
Criterion Covered Total %
statement 66 81 81.4
branch 31 46 67.3
condition n/a
subroutine 4 6 66.6
pod 4 4 100.0
total 105 137 76.6


line stmt bran cond sub pod time code
1             package Text::Fuzzy;
2             require Exporter;
3             require DynaLoader;
4              
5             @ISA = qw(Exporter DynaLoader);
6              
7             @EXPORT_OK = qw/fuzzy_index distance_edits/;
8             %EXPORT_TAGS = (
9             all => \@EXPORT_OK,
10             );
11              
12 12     12   763711 use warnings;
  12         116  
  12         433  
13 12     12   63 use strict;
  12         21  
  12         10343  
14             our $VERSION = '0.29';
15              
16             __PACKAGE__->bootstrap ($VERSION);
17              
18             # The following routine exports the C routines for the benefit of
19             # "CPAN::Nearest".
20              
21             sub dl_load_flags
22             {
23 12     12 1 3872 return 0x01;
24             }
25              
26             # This is a Perl-based edit distance routine which also returns the
27             # edit steps necessary to convert one string into the other. $distance
28             # is a boolean. If true it switches on
29              
30             our $verbose;
31              
32             sub distance_edits
33             {
34 0     0 1 0 return fuzzy_index (@_, 1);
35             }
36              
37             sub fuzzy_index
38             {
39             # $distance is usually 0 or undefined here.
40              
41 2     2 1 989 my ($needle, $haystack, $distance) = @_;
42              
43             # Test whether the inputs make any sense here.
44              
45 2         4 my $m = length ($needle);
46 2         4 my $n = length ($haystack);
47 2         3 my $longer;
48 2 100       4 if ($distance) {
49 1 50       3 $longer = $m > $n ? $m : $n;
50             }
51 2         24 my @haystack = split '', $haystack;
52 2         6 my @needle = split '', $needle;
53 2 50       5 print " ", join (" ",@haystack), "\n" if $verbose;
54 2         3 my @row1;
55 2 50       3 print " ", join (" ",@row1), "\n" if $verbose;
56 2         5 my @row2;
57             my @way;
58 2 100       3 if ($distance) {
59 1         5 for (0..$n) {
60 5         12 $way[0][$_] = "i" x $_;
61             }
62 1         3 @row1 = map {$_} (0..$n);
  5         7  
63             }
64             else {
65 1         7 @row1 = (0) x ($n + 1);
66 1         3 for (0..$n) {
67 84         97 $way[0][$_] = '';
68             }
69             }
70 2         5 for (0..$m) {
71 9         15 $way[$_][0] = "d" x $_;
72             }
73 2         5 for my $i (1..$m) {
74 7         9 $row2[0] = $i;
75 7 50       21 print "[", $needle[$i - 1], "] " if $verbose;
76 7 50       8 print $row2[0]," " if $verbose;
77 7         11 for my $j (1..$n) {
78 344         427 my $cost = ($needle[$i-1] ne $haystack[$j-1]);
79 344         324 my $deletion = $row1[$j] + 1;
80 344         336 my $insertion = $row2[$j-1] + 1;
81 344         332 my $substitution = $row1[$j-1] + $cost;
82 344         338 my $min;
83             my $way;
84 344         300 $min = $deletion;
85 344         339 $way = 'd';
86 344 100       422 if ($min > $insertion) {
87 15         16 $min = $insertion;
88 15         15 $way = 'i';
89             }
90 344 100       406 if ($min > $substitution) {
91 41 100       50 if ($cost) {
92 16         16 $way = 'r';
93             }
94             else {
95 25         25 $way = 'k';
96             }
97 41         38 $min = $substitution;
98             }
99 344 100       465 if ($way eq 'd') {
    100          
    50          
100 289 100       521 $way[$i][$j] = ($way[$i-1][$j] ? $way[$i-1][$j]:'') . $way;
101             }
102             elsif ($way eq 'i') {
103 14 50       30 $way[$i][$j] = ($way[$i][$j-1] ? $way[$i][$j-1]:'') . $way;
104             }
105             elsif ($way =~ /[kr]/) {
106 41 100       85 $way[$i][$j] = ($way[$i-1][$j-1] ? $way[$i-1][$j-1]:'') . $way;
107             }
108             else {
109 0         0 die "Internal bug: unrecognized path";
110             }
111 344         372 $row2[$j] = $min;
112 344 50       495 print $row2[$j],$way[$i][$j]," " if $verbose;
113             }
114 7         39 @row1 = @row2;
115 7 50       15 print "\n" if $verbose;
116             }
117 2 100       13 if ($distance) {
118 1         9 return ($row1[$n], $way[$m][$n]);
119             }
120             else {
121             # Windows doesn't like "inf" apparently.
122 1         2 my $mindistance = 1_000_000_000;
123 1         1 my $bestmatch;
124            
125 1         2 for my $j (1..$n) {
126              
127             # The best distance we have found so far.
128              
129 83 100       105 if ($row2[$j] < $mindistance) {
130 4         5 $bestmatch = $j;
131 4         5 $mindistance = $row2[$j];
132             }
133             }
134 1         23 return ($bestmatch, $way[$m][$bestmatch], $mindistance);
135             }
136             }
137              
138             sub nearestv
139             {
140 0     0 1   my ($tf, $array_ref) = @_;
141 0 0         if (wantarray) {
142 0           my @values;
143 0           my @offsets = $tf->nearest ($array_ref);
144 0 0         if (@offsets) {
145 0           for (@offsets) {
146 0           push @values, $array_ref->[$_];
147             }
148 0           return @values;
149             }
150             else {
151 0           return ();
152             }
153             }
154             else {
155 0           my $offset = $tf->nearest ($array_ref);
156 0 0         if (defined $offset) {
157 0           return $array_ref->[$offset];
158             }
159             else {
160 0           return undef;
161             }
162             }
163             }
164              
165              
166             1;