File Coverage

blib/lib/Compress/LZW/Progressive/Dict.pm
Criterion Covered Total %
statement 109 131 83.2
branch 32 46 69.5
condition 20 27 74.0
subroutine 23 27 85.1
pod 0 15 0.0
total 184 246 74.8


line stmt bran cond sub pod time code
1             package Compress::LZW::Progressive::Dict;
2              
3 3     3   38258 use strict;
  3         5  
  3         106  
4 3     3   13 use warnings;
  3         5  
  3         67  
5 3     3   4713 use bytes;
  3         30  
  3         13  
6              
7             our $VERSION = '0.1';
8              
9             sub new {
10 13     13 0 41 my ($class) = @_;
11              
12 13         60 my %self = (
13             tree => Compress::LZW::Progressive::Dict::Tree->new(),
14             # hash => {},
15             array => [],
16             next_code => 0,
17             reuse_codes => [],
18             codes_used => [],
19             code_counter => 0,
20             );
21              
22 13         38 my $self = bless \%self, $class;
23              
24 13         54 $self->add($_) foreach map { chr } 0..255;
  3328         5542  
25              
26 13         444 return $self;
27             }
28              
29             ## Adding and deleting from the dict
30              
31             sub add {
32 11801     11801 0 19571 my ($self, $phrase, $code) = @_;
33              
34 11801 50       21704 return 0 unless defined $phrase;
35 11801 50       31908 return 1 if $self->code($phrase); #defined $self->{hash}{$phrase};
36              
37 11801 50       27767 if (! defined $code) {
38 11801 100       14176 $code = int @{ $self->{reuse_codes} } ? shift @{ $self->{reuse_codes} } : $self->{next_code}++;
  11801         31159  
  2597         4975  
39             }
40              
41 11801         37269 my @chars = split //, $phrase;
42 11801         33873 $self->{tree}->add(\@chars, $code);
43              
44             # $self->{hash}{$phrase} = $code;
45 11801         26460 $self->{array}[$code] = $phrase;
46              
47 11801         35394 return $code;
48             }
49              
50             sub delete {
51 2876     2876 0 4032 my ($self, $phrase, $code) = @_;
52              
53 2876 50 33     11063 return 0 unless defined $phrase && defined $code;
54             # return 0 unless defined $self->{hash}{$phrase};
55 2876 50       6314 return 0 unless defined $self->{array}[$code];
56              
57 2876         7341 my @chars = split //, $phrase;
58 2876         7237 $self->{tree}->delete(\@chars);
59              
60             # delete $self->{hash}{$phrase};
61 2876         5508 $self->{array}[$code] = undef;
62            
63 2876         4243 $self->{codes_used}[$code] = undef;
64              
65 2876         2867 push @{ $self->{reuse_codes} }, $code;
  2876         4761  
66              
67 2876         11369 return 1;
68             }
69              
70             sub delete_phrase {
71 0     0 0 0 my ($self, $phrase) = @_;
72              
73 0         0 my $code = $self->{hash}{$phrase};
74 0         0 return $self->delete($phrase, $code);
75             }
76              
77             sub delete_code {
78 2     2 0 948 my ($self, $code) = @_;
79              
80 2         5 my $phrase = $self->{array}[$code];
81 2         9 return $self->delete($phrase, $code);
82             }
83              
84             sub delete_codes {
85 3     3 0 211 my ($self, @codes) = @_;
86 3         17 while (my $code = shift @codes) {
87 2874         5528 my $phrase = $self->{array}[$code];
88 2874 50       5455 return 0 unless $self->delete($phrase, $code);
89             }
90 3         31 return 1;
91             }
92              
93             ## Accessors
94              
95             sub code_matching_str {
96 11801     11801 0 13751 my ($self, $str) = @_;
97 11801         50838 return $self->code_matching_array([ split //, $str ]);
98             }
99             sub code_matching_array {
100 20361     20361 0 26767 my ($self, $arr) = @_;
101 20361         56169 return $self->{tree}->search(0, $arr);
102             }
103              
104             sub increment_code_usage_count {
105 34052     34052 0 45420 my ($self, $code) = @_;
106 34052         68680 $self->{codes_used}[$code] = $self->{code_counter}++;
107 34052         87649 return undef;
108             }
109              
110             sub next_code {
111 0     0 0 0 return $_[0]->{next_code};
112             }
113              
114             sub codes_used {
115 8473     8473 0 10546 my $self = shift;
116 8473         10860 return $self->{next_code} - int @{ $self->{reuse_codes} };
  8473         39064  
117             }
118              
119             # Given a count, return that many codes which haven't been used lately
120              
121             sub least_used_codes {
122 6     6 0 11 my ($self, $count) = @_;
123              
124 6         11 my $codes_used = $self->{codes_used};
125 160796         208941 my @delete =
126 22992         31441 sort { $codes_used->[$a] <=> $codes_used->[$b] }
127 6         1700 grep { defined $codes_used->[$_] }
128 6         13 256..$#{ $codes_used };
129              
130 6 50       608 $count = int(@delete) if int(@delete) < $count;
131              
132             # print join ', ', map { "$_ => $codes_used->[$_]" } @delete[0..($count-1)];
133             # print "\n";
134              
135 6         1901 return @delete[0..($count - 1)];
136             }
137              
138             sub phrase {
139 17028     17028 0 21830 my ($self, $code) = @_;
140              
141 17028         68598 return $self->{array}[$code];
142             }
143              
144             sub code {
145 11801     11801 0 16770 my ($self, $phrase) = @_;
146              
147             # return $self->{hash}{$phrase};
148 11801         20296 my $code = $self->code_matching_str($phrase);
149 11801 50 66     44026 if ($code && $self->phrase($code) eq $phrase) {
150 0         0 return $code;
151             }
152             else {
153 11801         29715 return undef;
154             }
155             }
156              
157             sub dump {
158 0     0 0 0 my ($self) = shift;
159              
160             # print "Phrase Hash\n";
161             # foreach my $phrase (keys %{ $self->{hash} }) {
162             # printf "%6d : %20s\n", $self->{hash}{$phrase}, $phrase;
163             # }
164              
165 0         0 print "Code Array\n";
166 0         0 foreach my $code (0..$#{ $self->{array} }) {
  0         0  
167 0 0       0 next unless defined $self->{array}[$code];
168 0         0 printf "%6d : %20s (%8d)\n", $code, $self->{array}[$code], $self->{codes_used}[$code];
169             }
170              
171 0         0 print "Next Code: ".$self->{next_code}."\n";
172 0         0 print "Reuse Codes:\n" . join(", ", @{ $self->{reuse_codes} }) . "\n";
  0         0  
173            
174             # return;
175 0         0 print "Tree\n";
176 0         0 $self->{tree}->print(0);
177             }
178              
179             package Compress::LZW::Progressive::Dict::Tree;
180              
181 3     3   3771 use strict;
  3         7  
  3         113  
182 3     3   17 use warnings;
  3         7  
  3         123  
183 3     3   17 no warnings 'recursion';
  3         33  
  3         433  
184 3     3   15 use bytes;
  3         6  
  3         34  
185              
186             our $VERSION = '0.11';
187              
188             sub new {
189 11826     11826   15196 my ($class) = @_;
190 11826 100       26800 $class = ref $class if ref $class;
191              
192 11826         40982 my @self = (
193             {},
194             undef,
195             );
196              
197 11826         57503 return bless \@self, $class;
198             }
199              
200             # Given an array of characters and a code, create children for each character and finally
201             # set the value of the final node
202              
203             sub add {
204 44449     44449   85878 my ($self, $chars, $code) = @_;
205              
206 44449         63752 my $char = shift @$chars;
207 44449 100       78323 if (defined $char) {
208 32648 100       66445 $char = 'null' if ord($char) == 0;
209 32648   66     102140 $self->[0]{$char} ||= $self->new();
210 32648         77493 $self->[0]{$char}->add($chars, $code);
211             }
212             else {
213 11801         40355 $self->[1] = $code;
214             }
215             }
216              
217             # Given an array and an index on that array, recursively delete all nodes from that point on and
218             # backwards while such nodes have no value
219              
220             sub delete {
221 9236     9236   10824 my ($self, $chars) = @_;
222              
223 9236         12276 my $char = shift @$chars;
224 9236 50 66     30535 $char = 'null' if defined $char && ord($char) == 0;
225              
226             # Descend to the last char
227 9236 100 66     49022 if (defined $char && (my $child = $self->[0]{$char})) {
    50          
228 6360 100       11137 if ($child->delete($chars)) {
229 2884         5807 delete $self->[0]{$char};
230             }
231             }
232             elsif (! defined $char) {
233 2876         4265 $self->[1] = undef;
234             }
235              
236             # Now, delete backwards unless I have children or a value
237 9236 100 100     9396 return (%{ $self->[0] } || defined $self->[1]) ? 0 : 1;
238             }
239              
240             # Given an array and an index on that array, recursively search for a defined node that matches
241             # as many as possible of the characters
242              
243             sub search {
244 62400     62400   102757 my ($self, $index, $arr) = @_;
245              
246 62400         79676 my $found_desc;
247              
248 62400         100232 my $char = $arr->[$index];
249 62400 100 100     277766 $char = 'null' if defined $char && ord($char) == 0;
250              
251 62400 100 100     365462 if (defined $char && (my $child = $self->[0]{$char})) {
252 42039         112830 $found_desc = $child->search($index + 1, $arr);
253 42039 100       126766 return $found_desc if defined $found_desc;
254             }
255              
256 20368 100 66     80278 if (! defined $found_desc && defined $self->[1]) {
257 17033         40338 return $self->[1];
258             }
259            
260 3335         12476 return undef;
261             }
262              
263             sub print {
264 0     0     my ($self, $level) = @_;
265            
266 0 0         print ' ' . (' 'x$level) . ' => ' . $self->[1] . "\n" if defined $self->[1];
267 0           foreach my $char (sort keys %{ $self->[0] }) {
  0            
268 0           print ' ' . (' 'x$level) . $char . "\n";
269 0           $self->[0]{$char}->print($level + 1);
270             }
271             }
272              
273             1;