File Coverage

blib/lib/Compress/Huffman.pm
Criterion Covered Total %
statement 158 178 88.7
branch 47 68 69.1
condition 4 12 33.3
subroutine 17 18 94.4
pod 9 10 90.0
total 235 286 82.1


line stmt bran cond sub pod time code
1             package Compress::Huffman;
2             require Exporter;
3             @ISA = qw(Exporter);
4             @EXPORT_OK = qw//;
5             %EXPORT_TAGS = (
6             all => \@EXPORT_OK,
7             );
8 3     3   201866 use warnings;
  3         24  
  3         127  
9 3     3   18 use strict;
  3         7  
  3         58  
10 3     3   15 use Carp;
  3         16  
  3         202  
11 3     3   20 use Scalar::Util 'looks_like_number';
  3         5  
  3         151  
12 3     3   1647 use POSIX qw/ceil/;
  3         19177  
  3         17  
13 3     3   5861 use JSON::Create '0.22', 'create_json';
  3         3249  
  3         463  
14 3     3   1384 use JSON::Parse '0.42', 'parse_json';
  3         3131  
  3         393  
15             our $VERSION = '0.08';
16              
17             # eps is the allowed floating point error for summing the values of
18             # the symbol table to ensure they form a probability distribution.
19              
20 3     3   24 use constant 'eps' => 0.0001;
  3         6  
  3         5607  
21              
22             # Private methods/functions
23              
24             # Add the prefix $i to everything underneath us.
25              
26             sub addcodetosubtable
27             {
28 199     199 0 352 my ($fakes, $h, $k, $size, $i) = @_;
29 199         290 my $subhuff = $fakes->{$k};
30 199         327 for my $j (0..$size - 1) {
31 403         522 my $subk = $subhuff->[$j];
32 403 100       743 if ($subk =~ /^fake/) {
33 139         276 addcodetosubtable ($fakes, $h, $subk, $size, $i);
34             }
35             else {
36 264         602 $h->{$subk} = $i . $h->{$subk};
37             }
38             }
39             }
40              
41             # Public methods below here
42              
43             sub new
44             {
45 5     5 1 848 return bless {};
46             }
47              
48             sub symbols
49             {
50             # Object and the table of symbols.
51 7     7 1 2767 my ($o, $s, %options) = @_;
52 7 100       26 if ($options{verbose}) {
53 5         17 $o->{verbose} = 1;
54             }
55             else {
56 2         10 $o->{verbose} = undef;
57             }
58             # Check $s is a hash reference.
59 7 50       29 if (ref $s ne 'HASH') {
60 0         0 croak "Use as \$o->symbols (\\\%symboltable, options...)";
61             }
62             # Copy the symbol table into our own thing. We need to put extra
63             # symbols in to it.
64 7         45 my %c = %$s;
65 7         30 $o->{c} = \%c;
66             # The number of symbols we encode with this Huffman code.
67 7         46 my $nentries = scalar keys %$s;
68 7 50       21 if (! $nentries) {
69 0         0 croak "Symbol table has no entries";
70             }
71             # Check we have numbers.
72 7         23 for my $k (keys %$s) {
73 81 50       171 if (! looks_like_number ($s->{$k})) {
74 0         0 croak "Non-numerical value '$s->{$k}' for key '$k'";
75             }
76             }
77 7 100       66 if ($o->{verbose}) {
78 5         104 print "Checked for numerical keys.\n";
79             }
80 7         23 my $size = $options{size};
81 7 100       20 if (! defined $size) {
82 5         10 $size = 2;
83             }
84 7 50 33     67 if ($size < 2 || int ($size) != $size) {
85 0         0 croak "Bad size $size for Huffman table, must be integer >= 2";
86             }
87 7 50 33     21 if ($size > 10 && ! $options{alphabet}) {
88 0         0 croak "Use \$o->symbols (\%t, alphabet => ['a', 'b',...]) for table sizes bigger than 10";
89             }
90 7 100       22 if ($o->{verbose}) {
91 5         42 print "Set size of Huffman code alphabet to $size.\n";
92             }
93             # If this is supposed to be a probability distribution, check
94 7         18 my $notprob = $options{notprob};
95 7 100       17 if ($notprob) {
96 4         24 for my $k (keys %$s) {
97 60         75 my $value = $s->{$k};
98 60 50       113 if ($value < 0.0) {
99 0         0 croak "Negative weight $value for symbol $k";
100             }
101             }
102             }
103             else {
104 3         6 my $total = 0.0;
105 3         9 for my $k (keys %$s) {
106 21         29 my $value = $s->{$k};
107 21 50 33     68 if ($value < 0.0 || $value > 1.0) {
108 0         0 croak "Value $value for symbol $k is not a probability; use \$o->symbols (\\\%s, notprob => 1) if not a probability distribution";
109             }
110 21         62 $total += $s->{$k};
111             }
112 3 50       12 if (abs ($total - 1.0) > eps) {
113 0         0 croak "Input values don't sum to 1.0; use \$o->symbols (\\\%s, notprob => 1) if not a probability distribution";
114             }
115 3 50       7 if ($o->{verbose}) {
116 3         31 print "Is a valid probability distribution (total = $total).\n";
117             }
118             }
119             # The number of tables. We need $t - 1 pointers to tables, which
120             # each require one table entry, so $t is the smallest number which
121             # satisfies
122             #
123             # $t * $size >= $nentries + $t - 1
124              
125 7         87 my $t = ceil (($nentries -1) / ($size - 1));
126 7 100       24 if ($o->{verbose}) {
127 5         55 print "This symbol table requires $t Huffman tables of size $size.\n";
128             }
129 7 100       66 if ($size > 2) {
130             # The number of dummy entries we need is
131 2         8 my $ndummies = $t * ($size - 1) - $nentries + 1;
132 2 50       6 if ($o->{verbose}) {
133 2         15 print "The Huffman tables need $ndummies dummy entries.\n";
134             }
135 2 50       8 if ($ndummies > 0) {
136             # Insert $ndummies dummy entries with probability zero into
137             # our copy of the symbol table.
138 0         0 for (0..$ndummies - 1) {
139 0         0 my $dummy = "dummy$_";
140 0 0       0 if ($c{$dummy}) {
141             # This is a bug not a user error.
142 0         0 die "The symbol table already has an entry '$dummy'";
143             }
144 0         0 $c{$dummy} = 0.0;
145             }
146             }
147             }
148             # The end-product, the Huffman encoding of the symbol table.
149 7         13 my %h;
150 7         13 my $nfake = 0;
151 7         11 my %fakes;
152 7         19 while ($nfake < $t) {
153 67 100       178 if ($o->{verbose}) {
154 15         112 print "Making key list for sub-table $nfake / $t.\n";
155             }
156 67         99 my $total = 0;
157 67         87 my @keys;
158              
159             # Find the $size keys with the minimum value and go through,
160             # picking them out.
161 67         121 for my $i (0..$size - 1) {
162             # This method is from
163             # https://stackoverflow.com/questions/1185822/how-do-i-create-or-test-for-nan-or-infinity-in-perl/1185828#1185828
164              
165             # inf doesn't work on some versions of Perl, see
166             # http://www.cpantesters.org/cpan/report/314e30b0-6bfb-1014-8e6c-c1e3e4f7669d
167 141         186 my $min = 9**9**9;
168 141         179 my $minkey;
169 141         611 for my $k (sort keys %c) {
170 1588 100       2763 if ($c{$k} < $min) {
171 237         307 $min = $c{$k};
172 237         348 $minkey = $k;
173             }
174             }
175 141         251 $total += $min;
176 141 100       238 if ($o->{verbose}) {
177 37         313 print "Choosing $minkey with $min for symbol $i\n";
178             }
179 141         250 delete $c{$minkey};
180 141         219 push @keys, $minkey;
181 141         304 $h{$minkey} = $i;
182             }
183             # The total weight of this table.
184             # The next table
185 67         90 my @huff;
186 67         111 for my $i (0..$size - 1) {
187 141         198 my $k = $keys[$i];
188 141 50       282 if (! defined $k) {
189 0         0 last;
190             }
191 141         235 push @huff, $k;
192 141 100       337 if ($k =~ /^fake/) {
193 60         128 addcodetosubtable (\%fakes, \%h, $k, $size, $i);
194             }
195             }
196 67         129 my $fakekey = 'fake'.$nfake;
197 67         123 $c{$fakekey} = $total;
198 67         447 $fakes{$fakekey} = \@huff;
199 67         162 $nfake++;
200             }
201 7 100       19 if ($o->{verbose}) {
202 5         37 print "Deleting dummy keys.\n";
203             }
204 7         55 for my $k (keys %h) {
205 141 100       337 if ($k =~ /fake|dummy/) {
206 60         100 delete $h{$k};
207             }
208             }
209 7         25 $o->{h} = \%h;
210 7         14 $o->{s} = $s;
211             # Blank this out for the case that the user inserts a new symbol
212             # table, etc.
213 7         14 $o->{value_re} = undef;
214 7         46 $o->{r} = undef;
215             }
216              
217             sub xl
218             {
219 1     1 1 292 my ($o) = @_;
220 1         3 my $h = $o->{h};
221 1         2 my $s = $o->{s};
222 1 50 33     7 croak "Bad object" unless $h && $s;
223 1         3 my $len = 0.0;
224 1         2 my $total = 0.0;
225 1         3 for my $k (keys %$h) {
226 3         10 $len += length ($h->{$k}) * $s->{$k};
227 3         5 $total += $s->{$k};
228 3 50       11 if ($o->{verbose}) {
229 3         24 print "$k $h->{$k} $s->{$k} $len\n";
230             }
231             }
232 1         6 return $len / $total;
233             }
234              
235             sub table
236             {
237 0     0 1 0 my ($o) = @_;
238 0         0 return $o->{h};
239             }
240              
241             sub encode_array
242             {
243 1     1 1 3 my ($o, $msg) = @_;
244 1         2 my @output;
245 1         3 for my $k (@$msg) {
246 29         46 my $h = $o->{h}{$k};
247 29 50       46 if (! defined $h) {
248 0         0 carp "Symbol '$k' is not in the symbol table";
249 0         0 next;
250             }
251 29         57 push @output, $h;
252             }
253 1         4 return \@output;
254             }
255              
256             sub encode
257             {
258 1     1 1 10 my ($o, $msg) = @_;
259 1         4 my $output = $o->encode_array ($msg);
260 1         7 return join '', @$output;
261             }
262              
263             sub decode
264             {
265 1     1 1 8 my ($o, $msg) = @_;
266 1 50       6 if (! $o->{value_re}) {
267 1         3 my @values = sort {length ($b) <=> length ($a)} values %{$o->{h}};
  93         129  
  1         7  
268 1         11 my $value_re = '(' . join ('|', @values) . ')';
269 1         3 $o->{value_re} = $value_re;
270 1 50       6 if ($o->{verbose}) {
271 0         0 print "Value regex is ", $o->{value_re}, "\n";
272             }
273             }
274 1 50       4 if (! $o->{r}) {
275 1         3 $o->{r} = {reverse %{$o->{h}}};
  1         19  
276             }
277 1         3 my @output;
278 1         77 while ($msg =~ s/^$o->{value_re}//) {
279 29         145 push @output, $o->{r}{$1};
280             }
281 1 50       3 if (length ($msg) > 0) {
282 0         0 carp "Input starting from $msg was not Huffman encoded using this table";
283             }
284 1         4 return \@output;
285             }
286              
287             sub save
288             {
289 1     1 1 7 my ($o) = @_;
290 1         16 return create_json ($o);
291             }
292              
293             sub load
294             {
295 1     1 1 7 my ($o, $data) = @_;
296 1         14 my $input = parse_json ($data);
297 1         5 for my $k (keys %$input) {
298 6         12 $o->{$k} = $input->{$k};
299             }
300             }
301              
302              
303             1;