File Coverage

blib/lib/String/Eertree.pm
Criterion Covered Total %
statement 70 70 100.0
branch 14 16 87.5
condition 5 6 83.3
subroutine 14 14 100.0
pod 2 8 25.0
total 105 114 92.1


line stmt bran cond sub pod time code
1             package String::Eertree;
2 6     6   1027665 use warnings;
  6         49  
  6         188  
3 6     6   31 use strict;
  6         27  
  6         135  
4              
5 6     6   3226 use Syntax::Construct qw{ // };
  6         18224  
  6         55  
6              
7 6     6   3258 use Moo;
  6         56603  
  6         28  
8              
9 6     6   10155 use String::Eertree::Node;
  6         17  
  6         5708  
10              
11             has nodes => (is => 'ro', default => sub { [
12             'String::Eertree::Node'->new(link => 0, length => -1, pos => -1),
13             'String::Eertree::Node'->new(link => 0, length => 0, pos => 0)
14             ]});
15             has string => (is => 'ro', required => 1);
16             has max => (is => 'rwp', default => 0);
17             has _count_finished => (is => 'rw', default => 0);
18              
19             sub node {
20 1486     1486 0 2175 my ($self, $index) = @_;
21 1486 50       2463 die "Invalid index $index." if $index < 0;
22              
23 1486         4901 return $self->nodes->[$index]
24             }
25              
26             sub at {
27 468     468 0 683 my ($self, $pos) = @_;
28 468         1644 return substr $self->string, $pos, 1
29             }
30              
31             sub BUILD {
32 16     16 0 8708 my ($self) = @_;
33 16         32 my $i = 0;
34 16         129 $self->add($i++, $_) for split //, $self->string;
35             };
36              
37             sub Push {
38 194     194 0 355 my ($self, $node) = @_;
39 194         248 push @{ $self->nodes }, $node;
  194         450  
40             }
41              
42 392     392 0 523 sub Last { $#{ $_[0]->nodes } }
  392         3642  
43              
44             sub add {
45 200     200 0 390 my ($self, $index, $char) = @_;
46              
47 200         288 my $new_node;
48 200         328 my $p = $self->max;
49 200         423 while ($self->node($p)) {
50 421         720 my $node = $self->node($p);
51 421 100       944 my $pos = $node->length == -1
52             ? $index
53             : $index - $node->length - 1;
54 421 100 100     901 if ($pos >= 0 && $self->at($pos) eq $char) {
55 200 100       3305 if (exists $node->edge->{$char}) {
56 6         120 $new_node = $self->node($node->edge->{$char});
57 6         20 $new_node->increment_count;
58 6         93 $self->_set_max($node->edge->{$char});
59             return
60 6         58 }
61 194         3284 $new_node = 'String::Eertree::Node'->new(
62             pos => $pos,
63             length => $index - $pos + 1);
64 194         3847 $node->edge->{$char} = $self->Last + 1;
65             last
66 194         1358 }
67 221         460 $p = $node->link;
68             }
69              
70 194         471 $self->Push($new_node);
71 194         356 $self->_set_max($self->Last);
72              
73 194 100       480 if ($new_node->length == 1) {
74 62         144 $new_node->_set_link(1);
75             return
76 62         184 }
77              
78 132         224 my $q = $self->node($p)->link;
79 132         190 while (1) {
80 166 100       256 my $pos = $self->node($q)->length == -1
81             ? $index
82             : $index - $self->node($q)->length - 1;
83 166 100 66     395 if ($pos >= 0 && $self->at($pos) eq $char) {
84 132         241 $new_node->_set_link($self->node($q)->edge->{$char});
85             last
86 132         1221 }
87 34         83 $q = $self->node($q)->link;
88             }
89             }
90              
91             sub uniq_palindromes {
92 10     10 1 35 my ($self) = @_;
93 10         17 return grep length, map $_->string($self), @{ $self->nodes }
  10         34  
94             }
95              
96             sub palindromes {
97 2     2 1 15 my ($self) = @_;
98 2         7 $self->_count;
99             return map {
100 20         53 grep length, ($_->string($self)) x $_->count
101 2         2 } @{ $self->nodes }
  2         8  
102             }
103              
104             sub _count {
105 2     2   4 my ($self) = @_;
106 2 50       7 return if $self->_count_finished;
107              
108 2         5 $self->_count_finished(1);
109 2         17 for my $node (reverse @{ $self->nodes }) {
  2         9  
110 20         36 $self->node($node->link)->increment_count($node->count);
111             }
112             }
113              
114             =head1 NAME
115              
116             String::Eertree - Build the palindromic tree aka Eertree for a string
117              
118             =head1 VERSION
119              
120             Version 0.03
121              
122             =cut
123              
124             our $VERSION = '0.03';
125              
126             =head1 SYNOPSIS
127              
128             Eertrees make it possible to find palindrome substrings of a string in a very
129             fast way.
130              
131             use String::Eertree;
132              
133             my $tree = 'String::Eertree'->new(string => 'referee');
134             my @palindromes = $tree->uniq_palindromes; # r e f efe refer ere ee
135              
136             To see how fast it is, check the file F. It compares
137             the speed of the Eertree algorithm to a naive generation of all the unique
138             palindromes as found at L
139             Code|https://rosettacode.org/wiki/Eertree#Perl>. Eertree is almost 40 times
140             faster on a string of length 79.
141              
142             =head1 METHODS
143              
144             =head2 new
145              
146             'String::Eertree'->new(string => 'xxx')
147              
148             The constructor. Use the named argument C to specify the string you
149             want to analyse.
150              
151             =head2 string
152              
153             my $string = $tree->string;
154              
155             The original string the tree was constructed from (see above).
156              
157             =head2 uniq_palindromes
158              
159             my @palindromes = $tree->uniq_palindromes;
160              
161             Returns all distinct palindrome substrings of the string.
162              
163             =head2 palindromes
164              
165             my @palindromes = $tree->palindromes;
166              
167             Returns all the palindrome substrings of the string, each substring can be
168             repeated if it's present at different positions in the string.
169              
170             =head1 AUTHOR
171              
172             E. Choroba, C<< >>
173              
174             =head1 BUGS
175              
176             Please report any bugs or feature requests to C, or through
177             the web interface at L. I will be notified, and then you'll
178             automatically be notified of progress on your bug as I make changes.
179              
180             =head1 SUPPORT
181              
182             You can find documentation for this module with the perldoc command.
183              
184             perldoc String::Eertree
185              
186              
187             You can also look for information at:
188              
189             =over 4
190              
191             =item * RT: CPAN's request tracker (report bugs here)
192              
193             L
194              
195             =item * Search CPAN
196              
197             L
198              
199             =back
200              
201             =head1 ACKNOWLEDGEMENTS
202              
203             Thanks Mohammad S Anwar (MANWAR) for introducing me to the idea.
204              
205             Thanks L for a clean Python
206             implementation.
207              
208             Thanks Mikhail Rubinchik and Arseny M. Shur for inventing the eertree
209             (arXiv:1506.04862v2 [cs.DS] 17 Aug 2015).
210              
211             =head1 LICENSE AND COPYRIGHT
212              
213             This software is Copyright (c) 2022-2023 by E. Choroba.
214              
215             This is free software, licensed under:
216              
217             The Artistic License 2.0 (GPL Compatible)
218              
219             =cut
220              
221             __PACKAGE__