File Coverage

lib/Text/Levenshtein/BV.pm
Criterion Covered Total %
statement 203 224 90.6
branch 36 44 81.8
condition 43 44 97.7
subroutine 13 17 76.4
pod 8 8 100.0
total 303 337 89.9


line stmt bran cond sub pod time code
1             package Text::Levenshtein::BV;
2              
3 3     3   152034 use strict;
  3         27  
  3         93  
4 3     3   14 use warnings;
  3         5  
  3         119  
5             our $VERSION = '0.06';
6              
7 3     3   13 use utf8;
  3         5  
  3         16  
8              
9 3     3   145 use 5.010001;
  3         10  
10              
11             our $width = int 0.999+log(~0)/log(2);
12              
13 3     3   1629 use integer;
  3         46  
  3         15  
14 3     3   102 no warnings 'portable'; # for 0xffffffffffffffff
  3         6  
  3         417  
15              
16             our @masks = (
17             # 0x0000000000000000,
18             0x0000000000000001,0x0000000000000003,0x0000000000000007,0x000000000000000f,
19             0x000000000000001f,0x000000000000003f,0x000000000000007f,0x00000000000000ff,
20             0x00000000000001ff,0x00000000000003ff,0x00000000000007ff,0x0000000000000fff,
21             0x0000000000001fff,0x0000000000003fff,0x0000000000007fff,0x000000000000ffff,
22             0x000000000001ffff,0x000000000003ffff,0x000000000007ffff,0x00000000000fffff,
23             0x00000000001fffff,0x00000000003fffff,0x00000000007fffff,0x0000000000ffffff,
24             0x0000000001ffffff,0x0000000003ffffff,0x0000000007ffffff,0x000000000fffffff,
25             0x000000001fffffff,0x000000003fffffff,0x000000007fffffff,0x00000000ffffffff,
26             0x00000001ffffffff,0x00000003ffffffff,0x00000007ffffffff,0x0000000fffffffff,
27             0x0000001fffffffff,0x0000003fffffffff,0x0000007fffffffff,0x000000ffffffffff,
28             0x000001ffffffffff,0x000003ffffffffff,0x000007ffffffffff,0x00000fffffffffff,
29             0x00001fffffffffff,0x00003fffffffffff,0x00007fffffffffff,0x0000ffffffffffff,
30             0x0001ffffffffffff,0x0003ffffffffffff,0x0007ffffffffffff,0x000fffffffffffff,
31             0x001fffffffffffff,0x003fffffffffffff,0x007fffffffffffff,0x00ffffffffffffff,
32             0x01ffffffffffffff,0x03ffffffffffffff,0x07ffffffffffffff,0x0fffffffffffffff,
33             0x1fffffffffffffff,0x3fffffffffffffff,0x7fffffffffffffff,0xffffffffffffffff,
34             );
35              
36             sub new {
37 8     8 1 12713 my $class = shift;
38              
39 8 100 66     74 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  2 100       18  
40             }
41              
42 3     3   1903 use Data::Dumper;
  3         21700  
  3         7149  
43              
44             sub SES {
45 812     812 1 4991089 my ($self, $a, $b) = @_;
46              
47 812 100 100     2500 if ( !scalar(@$a) && !scalar(@$b) ) { return [] }
  2         12  
48              
49 810         1648 my ($amin, $amax, $bmin, $bmax) = (0, $#$a, 0, $#$b);
50              
51             # NOTE: prefix / suffix optimisation does not work reliable yet
52 810         923 if (0) {
53             while ($amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin]) {
54             $amin++;
55             $bmin++;
56             }
57             while ($amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax]) {
58             $amax--;
59             $bmax--;
60             }
61              
62             ##print '$amin: ',$amin, ' $amax: ',$amax, ' $bmin: ',$bmin, ' $bmax: ',$bmax, "\n";
63             # if one of the sequences is a complete subset of the other
64              
65             if ( ($amax < $amin) && ($bmax < $bmin) ) {
66             return [
67             map( [$_ => $_], 0 .. $#$b ),
68             ];
69             }
70             elsif ( ($amax < $amin) ) {
71             return [
72             map( [$_ => $_], 0 .. ($bmin-1) ),
73             map( ['-1' => $_], $bmin .. $bmax ),
74             map( [++$amax => $_], ($bmax+1) .. $#$b )
75             ];
76             }
77             elsif ( ($bmax < $bmin) ) {
78             return [
79             map( [$_ => $_], 0 .. ($amin-1) ),
80             map( [$_ => '-1'], $amin .. $amax ),
81             map( [$_ => ++$bmax], ($amax+1) .. $#$a )
82             ];
83             }
84             }
85              
86 810         1050 my $positions;
87              
88 810 100       1751 if (($amax - $amin) < $width ) {
89 783         4426 $positions->{$a->[$_+$amin]} |= 1 << $_ for 0..($amax-$amin);
90              
91 783         1321 my $VPs = [];
92 783         1006 my $VNs = [];
93 783         882 my $VP = ~0;
94 783         914 my $VN = 0;
95              
96 783         1016 my ($PM, $X, $D0, $HN, $HP);
97              
98             # outer loop [HN02] Fig. 7
99 783         1305 for my $j ( $bmin .. $bmax ) {
100 3718   100     7448 $PM = $positions->{$b->[$j]} // 0;
101 3718         3935 $X = $PM | $VN;
102 3718         4232 $D0 = (($VP + ($X & $VP)) ^ $VP) | $X;
103 3718         3622 $HN = $VP & $D0;
104 3718         4240 $HP = $VN | ~($VP|$D0);
105 3718         3754 $X = ($HP << 1) | 1;
106 3718         3652 $VN = $X & $D0;
107 3718         4083 $VP = ($HN << 1) | ~($X | $D0);
108 3718         4119 $VPs->[$j] = $VP;
109 3718         4601 $VNs->[$j] = $VN;
110             }
111             return [
112             #map( [$_ => $_], 0 .. ($bmin-1) ) ,
113 783         1492 _backtrace($VPs, $VNs, $amin, $amax, $bmin, $bmax),
114             #map( [++$amax => $_], ($bmax+1) .. $#$b )
115             ];
116             }
117             else {
118              
119 27         98 my $m = $amax-$amin +1;
120 27         38 my $diff = $m;
121              
122 27         69 my $kmax = ($m) / $width;
123 27 100       97 $kmax++ if (($m) % $width);
124              
125 27         3270 $positions->{$a->[$_+$amin]}->[$_/$width] |= 1 << ($_ % $width) for 0..($amax-$amin);
126              
127 27         68 my @mask;
128              
129 27         103 $mask[$_] = 0 for (0..$kmax-1);
130 27         74 $mask[$kmax-1] = 1 << (($m-1) % $width);
131              
132 27         53 my @VPs;
133 27         1179 $VPs[$_ / $width] |= 1 << ($_ % $width) for 0..$m-1;
134              
135 27         52 my @VNs;
136 27         111 $VNs[$_] = 0 for (0..$kmax-1);
137              
138 27         136 my $VPS = [];
139 27         103 my $VNS = [];
140              
141 27         151 my ($PM,$X,$D0,$HN,$HP);
142              
143 27         0 my $HNcarry;
144 27         0 my $HPcarry;
145              
146 27         82 for my $j ( $bmin .. $bmax ) {
147              
148 2979         3067 $HNcarry = 0;
149 2979         2616 $HPcarry = 1;
150 2979         4488 for (my $k=0; $k < $kmax; $k++ ) {
151 9944   100     21170 $PM = $positions->{$b->[$j]}->[$k] // 0;
152 9944         10870 $X = $PM | $HNcarry | $VNs[$k];
153 9944         11752 $D0 = (($VPs[$k] + ($X & $VPs[$k])) ^ $VPs[$k]) | $X;
154 9944         9633 $HN = $VPs[$k] & $D0;
155 9944         10699 $HP = $VNs[$k] | ~($VPs[$k] | $D0);
156 9944         10143 $X = ($HP << 1) | $HPcarry;
157 9944         10541 $HPcarry = $HP >> ($width-1) & 1;
158 9944         10150 $VNs[$k] = ($X & $D0);
159 9944         10671 $VPs[$k] = ($HN << 1) | ($HNcarry) | ~($X | $D0);
160              
161 9944         13009 $VPS->[$j][$k] = $VPs[$k];
162 9944         11880 $VNS->[$j][$k] = $VNs[$k];
163              
164 9944         16014 $HNcarry = $HN >> ($width-1) & 1;
165             }
166             }
167             return [
168             #map([$_ => $_], 0 .. ($bmin-1)),
169 27         141 _backtrace2($VPS, $VNS, $amin, $amax, $bmin, $bmax),
170             #map([++$amax => $_], ($bmax+1) .. $#$b)
171             ];
172             }
173             }
174              
175             # Hyyrö, Heikki. (2004). A Note on Bit-Parallel Alignment Computation. 79-87.
176             # Fig. 3
177             sub _backtrace {
178 783     783   1527 my ($VPs, $VNs, $amin, $amax, $bmin, $bmax) = @_;
179              
180             # recover alignment
181 783         924 my $i = $amax;
182 783         939 my $j = $bmax;
183              
184 783         1030 my @ses = ();
185              
186 783         967 my $none = '-1';
187              
188 783   100     2516 while ($i >= $amin && $j >= $bmin) {
189 3818 100       5685 if ($VPs->[$j] & (1<<$i)) {
190 467         862 unshift @ses,[$i, $none];
191 467         1143 $i--;
192             }
193             else {
194 3351 100 100     7809 if (($j > 0) && ($VNs->[$j-1] & (1<<$i))) {
195 1117         2101 unshift @ses, [$none, $j];
196 1117         2865 $j--;
197             }
198             else {
199 2234         3920 unshift @ses, [$i, $j];
200 2234         2320 $i--;$j--;
  2234         5019  
201             }
202             }
203             }
204 783         1259 while ($i >= $amin) {
205 210         416 unshift @ses,[$i+$amin,$none];
206 210         326 $i--;
207             }
208 783         1235 while ($j >= $bmin) {
209 367         560 unshift @ses,[$none,$j];
210 367         608 $j--;
211             }
212 783         4567 return @ses;
213             }
214              
215             sub _backtrace2 {
216 27     27   104 my ($VPs, $VNs, $amin, $amax, $bmin, $bmax) = @_;
217              
218             # recover alignment
219 27         53 my $i = $amax;
220 27         37 my $j = $bmax;
221              
222 27         40 my @ses = ();
223              
224 27         60 my $none = '-1';
225              
226 27   100     127 while ($i >= $amin && $j >= $bmin) {
227 4290         4665 my $k = $i / $width;
228 4290 100       6134 if ( $VPs->[$j]->[$k] & (1<<($i % $width)) ) {
229 1317         2044 unshift @ses, [$i, $none];
230 1317         3098 $i--;
231             }
232             else {
233 2973 100 100     7313 if ( ($j > 0) && ($VNs->[$j-1]->[$k] & (1<<($i % $width))) ) {
234             ##if ( ($VNs->[$j-1]->[$k] & (1<<($i % $width))) ) {
235 466         857 unshift @ses, [$none, $j];
236 466         1130 $j--;
237             }
238             else {
239 2507         4891 unshift @ses, [$i, $j];
240 2507         2723 $i--;$j--;
  2507         6053  
241             }
242             }
243             }
244 27         80 while ($i >= $amin) {
245 10         21 unshift @ses, [$i, $none];
246 10         17 $i--;
247             }
248 27         67 while ($j >= $bmin) {
249 6         21 unshift @ses, [$none, $j];
250 6         16 $j--;
251             }
252 27         1947 return @ses;
253             }
254              
255              
256             my $diag = 0;
257              
258             # [HN02] Fig. 3 -> Fig. 7
259             sub distance {
260 812     812 1 5025404 my ($self, $a, $b) = @_;
261              
262             #TODO: if (@b < @a) {$a = $b; $b = $a}
263              
264             ##if ( !scalar(@$a) && !scalar(@$b) ) { return 0 }
265              
266 812         2124 my ($amin, $amax, $bmin, $bmax) = (0, $#$a, 0, $#$b);
267              
268 812         1018 if (1) {
269 812   100     4501 while ($amin <= $amax and $bmin <= $bmax and $a->[$amin] eq $b->[$bmin]) {
      100        
270 678         725 $amin++;
271 678         2018 $bmin++;
272             }
273 812   100     3133 while ($amin <= $amax and $bmin <= $bmax and $a->[$amax] eq $b->[$bmax]) {
      100        
274 533         554 $amax--;
275 533         1527 $bmax--;
276             }
277             }
278              
279             # if one of the sequences is a complete subset of the other,
280             # return difference of lengths.
281 812 100 100     2160 if (($amax < $amin) || ($bmax < $bmin)) { return abs(@$a - @$b); }
  369         1852  
282              
283 443         513 my $positions;
284              
285 443 100       933 if (($amax - $amin) < $width ) {
286              
287 418         2324 $positions->{$a->[$_+$amin]} |= 1 << $_ for 0..($amax-$amin);
288              
289 418         643 my $m = $amax-$amin +1;
290 418         501 my $diff = $m;
291              
292 418         512 my $m_mask = 1 << $m-1;
293              
294 418         481 my $VP = 0;
295 418         928 $VP |= 1 << $_ for 0..$m-1; # TODO: cache table
296              
297 418         486 my $VN = 0;
298              
299 418         579 my ($PM,$X,$D0,$HN,$HP);
300              
301             # outer loop [HN02] Fig. 7
302 418         641 for my $j ( $bmin .. $bmax ) {
303 2291   100     4451 $PM = $positions->{$b->[$j]} // 0;
304 2291         2251 $X = $PM | $VN;
305 2291         2543 $D0 = (($VP + ($X & $VP)) ^ $VP) | $X;
306 2291         2289 $HN = $VP & $D0;
307 2291         2428 $HP = $VN | ~($VP|$D0);
308 2291         2318 $X = ($HP << 1) | 1;
309 2291         2209 $VN = $X & $D0;
310 2291         2535 $VP = ($HN << 1) | ~($X | $D0);
311              
312 2291 100       3019 if ($HP & $m_mask) { $diff++; };
  995         968  
313 2291 100       3346 if ($HN & $m_mask) { $diff--; };
  320         373  
314              
315             }
316 418         2549 return $diff;
317             }
318             else {
319              
320 25         68 my $m = $amax-$amin +1;
321 25         50 my $diff = $m;
322              
323 25         71 my $kmax = ($m) / $width;
324 25 100       76 $kmax++ if (($m) % $width);
325              
326             # m * 3
327 25         2757 $positions->{$a->[$_+$amin]}->[$_ / $width] |= 1 << ($_ % $width) for 0..($amax-$amin);
328              
329 25         57 my @mask;
330              
331 25         107 $mask[$_] = 0 for (0..$kmax-1);
332 25         66 $mask[$kmax-1] = 1 << (($m-1) % $width);
333              
334 25         34 my @VPs;
335 25         1090 $VPs[$_ / $width] |= 1 << ($_ % $width) for 0..$m-1;
336              
337 25         45 my @VNs;
338 25         83 $VNs[$_] = 0 for (0..$kmax-1);
339              
340 25         72 my ($PM,$X,$D0,$HN,$HP);
341              
342 25         0 my $HNcarry;
343 25         0 my $HPcarry;
344              
345 25         57 for my $j ( $bmin .. $bmax ) {
346              
347 2836         2722 $HNcarry = 0;
348 2836         2451 $HPcarry = 1;
349 2836         3857 for (my $k=0; $k < $kmax; $k++ ) {
350 9658   100     18286 $PM = $positions->{$b->[$j]}->[$k] // 0;
351 9658         10010 $X = $PM | $HNcarry | $VNs[$k];
352 9658         11037 $D0 = (($VPs[$k] + ($X & $VPs[$k])) ^ $VPs[$k]) | $X;
353 9658         9721 $HN = $VPs[$k] & $D0;
354 9658         10047 $HP = $VNs[$k] | ~($VPs[$k] | $D0);
355 9658         9494 $X = ($HP << 1) | $HPcarry;
356 9658         9913 $HPcarry = $HP >> ($width-1) & 1;
357 9658         9484 $VNs[$k] = ($X & $D0);
358 9658         10247 $VPs[$k] = ($HN << 1) | ($HNcarry) | ~($X | $D0);
359 9658         10417 $HNcarry = $HN >> ($width-1) & 1;
360              
361 9658 100       13097 if ($HP & $mask[$k]) { $diff++; }
  335         319  
362 9658 100       16253 if ($HN & $mask[$k]) { $diff--; }
  752         1157  
363             }
364             }
365 25         531 return $diff;
366             }
367             }
368              
369             sub sequences2hunks {
370 0     0 1 0 my ($self, $a, $b) = @_;
371 0         0 return [ map { [ $a->[$_], $b->[$_] ] } 0..$#$a ];
  0         0  
372             }
373              
374             sub hunks2sequences {
375 0     0 1 0 my ($self, $hunks) = @_;
376              
377 0         0 my $a = [];
378 0         0 my $b = [];
379              
380 0         0 for my $hunk (@$hunks) {
381 0         0 push @$a, $hunk->[0];
382 0         0 push @$b, $hunk->[1];
383             }
384 0         0 return ($a,$b);
385             }
386              
387             sub sequence2char {
388 0     0 1 0 my ($self, $a, $sequence, $gap) = @_;
389              
390 0 0       0 $gap = (defined $gap) ? $gap : '_';
391              
392 0 0       0 return [ map { ($_ >= 0) ? $a->[$_] : $gap } @$sequence ];
  0         0  
393             }
394              
395             sub hunks2distance {
396 812     812 1 1948 my ($self, $a, $b, $hunks) = @_;
397              
398 812         912 my $distance = 0;
399              
400 812         1283 for my $hunk (@$hunks) {
401 8701 100 100     22562 if (($hunk->[0] < 0) || ($hunk->[1] < 0)) { $distance++ }
  3960 100       4399  
402 2191         2563 elsif ($a->[$hunk->[0]] ne $b->[$hunk->[1]]) { $distance++ }
403             }
404 812         3804 return $distance;
405             }
406              
407             sub hunks2char {
408 0     0 1   my ($self, $a, $b, $hunks) = @_;
409              
410 0           my $chars = [];
411              
412 0           for my $hunk (@$hunks) {
413 0 0         my $char1 = ($hunk->[0] >= 0) ? $a->[$hunk->[0]] : '_';
414 0 0         my $char2 = ($hunk->[1] >= 0) ? $a->[$hunk->[1]] : '_';
415              
416 0           push @$chars, [$char1,$char2];
417             }
418 0           return $chars;
419             }
420              
421             1;
422              
423             __END__