File Coverage

blib/lib/Algorithm/Huffman.pm
Criterion Covered Total %
statement 44 137 32.1
branch 8 22 36.3
condition 2 6 33.3
subroutine 11 20 55.0
pod 7 7 100.0
total 72 192 37.5


line stmt bran cond sub pod time code
1             package Algorithm::Huffman;
2              
3 4     4   10795 use 5.006;
  4         14  
  4         202  
4 4     4   101 use strict;
  4         6  
  4         306  
5 4     4   1233 use warnings;
  4         20  
  4         117  
6 4     4   20 use Carp;
  4         5  
  4         504  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             our $VERSION = '0.09';
13              
14 4     4   3391 use Heap::Fibonacci;
  4         9028  
  4         295  
15 4     4   5627 use Tree::DAG_Node;
  4         119228  
  4         182  
16 4     4   52 use List::Util qw/max min first/;
  4         9  
  4         5381  
17              
18             sub new {
19 12     12 1 228279 my ($proto, $count_hash) = @_;
20 12   33     64 my $class = ref($proto) || $proto;
21            
22 12         36 __validate_counting_hash($count_hash);
23 6         39 my $heap = Heap::Fibonacci->new;
24            
25 6         45 my $size = 0;
26 6         26 while (my ($str, $count) = each %$count_hash) {
27 6 50       52 croak "The count for each character/substring must be a number"
28             unless $count =~ /^(-)?\d+(\.\d+)?$/;
29 6 50       14 croak "The count for each character/substring must be positive (>= 0)," .
30             "but found counting '$count' for the string '$str'"
31             unless $count >= 0;
32 6         50 my $leaf = Tree::DAG_Node->new({name => $str});
33 6         313 $leaf->attribute->{bit} = "";
34 6         141 $heap->add( KeyValuePair->new( $leaf, $count ) );
35 0         0 $size++;
36             }
37            
38 0         0 while ($size-- >= 2) {
39 0         0 my $right = $heap->extract_minimum;
40 0         0 my $left = $heap->extract_minimum;
41 0         0 $right->key->attribute->{bit} = 1;
42 0         0 $left->key->attribute->{bit} = 0;
43 0         0 my $new_node = Tree::DAG_Node->new({daughters => [$left->key, $right->key]});
44 0         0 $new_node->attribute->{bit} = "";
45 0         0 my $new_count = $left->value + $right->value;
46 0         0 $heap->add( KeyValuePair->new( $new_node, $new_count ) );
47             }
48            
49 0         0 my $root = $heap->extract_minimum->key;
50            
51 0         0 my %encode;
52             my %decode;
53 0         0 foreach my $leaf ($root->leaves_under) {
54 0         0 my @bit = reverse map {$_->attribute->{bit}} ($leaf, $leaf->ancestors);
  0         0  
55 0         0 my $bitstr = join "", @bit;
56 0         0 $encode{$leaf->name} = $bitstr;
57 0         0 $decode{$bitstr} = $leaf->name;
58             }
59            
60 0         0 my $self = {
61             encode => \%encode,
62             decode => \%decode,
63             max_length_encoding_key => max( map length, keys %encode ),
64             max_length_decoding_key => max( map length, keys %decode ),
65             min_length_decoding_key => min( map length, keys %decode )
66             };
67            
68 0         0 bless $self, $class;
69             }
70              
71             sub encode_hash {
72 0     0 1 0 my $self = shift;
73 0         0 $self->{encode};
74             }
75              
76             sub decode_hash {
77 0     0 1 0 my $self = shift;
78 0         0 $self->{decode};
79             }
80              
81             sub encode_bitstring {
82 0     0 1 0 my ($self, $string) = @_;
83 0         0 my $max_length_encoding_key = $self->{max_length_encoding_key};
84 0         0 my %encode_hash = %{$self->encode_hash};
  0         0  
85              
86 0         0 my $bitstring = "";
87 0         0 my ($index, $max_index) = (0, length($string)-1);
88 0         0 while ($index <= $max_index) {
89 0         0 for (my $l = $max_length_encoding_key; $l > 0; $l--) {
90 0 0       0 if (my $bits = $encode_hash{substr($string, $index, $l)}) {
91 0         0 $bitstring .= $bits;
92 0         0 $index += $l;
93 0         0 last;
94             }
95             }
96             }
97 0         0 return $bitstring;
98             }
99              
100             sub encode {
101 0     0 1 0 my ($self, $string) = @_;
102 0         0 my $max_length_encoding_key = $self->{max_length_encoding_key};
103 0         0 my %encode_hash = %{$self->encode_hash};
  0         0  
104              
105 0         0 my $bitvector = "";
106 0         0 my $offset = 0;
107 0         0 my ($index, $max_index) = (0, length($string)-1);
108 0         0 while ($index <= $max_index) {
109 0         0 for (my $l = $max_length_encoding_key; $l > 0; $l--) {
110 0 0       0 if (my $bits = $encode_hash{substr($string, $index, $l)}) {
111 0         0 vec($bitvector, $offset++, 1) = $_ for split //, $bits;
112 0         0 $index += $l;
113 0         0 last;
114             }
115             }
116             }
117 0         0 return $bitvector;
118             }
119              
120             sub decode_bitstring {
121 0     0 1 0 my ($self, $bitstring) = @_;
122            
123 0         0 my $max_length_decoding_key = $self->{max_length_decoding_key};
124 0         0 my $min_length_decoding_key = $self->{min_length_decoding_key};
125 0         0 my %decode_hash = %{$self->decode_hash};
  0         0  
126            
127 0         0 my $string = "";
128 0         0 my ($index, $max_index) = (0, length($bitstring)-1);
129 0         0 while ($index < $max_index) {
130 0         0 my $decode = undef;
131 0         0 foreach my $l ($min_length_decoding_key .. $max_length_decoding_key) {
132 0 0       0 if ($decode = $decode_hash{substr($bitstring,$index,$l)}) {
133 0         0 $string .= $decode;
134 0         0 $index += $l;
135 0         0 last;
136             }
137             }
138 0 0       0 defined $decode
139             or die "Unknown bit sequence starting at index $index in the bitstring";
140             }
141 0         0 return $string;
142             }
143              
144             sub decode {
145 0     0 1 0 my ($self, $bitvector) = @_;
146            
147 0         0 my $max_length_decoding_key = $self->{max_length_decoding_key};
148 0         0 my $min_length_decoding_key = $self->{min_length_decoding_key};
149 0         0 my %decode_hash = %{$self->decode_hash};
  0         0  
150            
151 0         0 my $string = "";
152 0         0 my ($offset, $max_offset) = (0, 8 * (length($bitvector)-1));
153 0         0 while ($offset < $max_offset) {
154 0         0 my $decode = undef;
155 0         0 my $bitpattern = "";
156 0         0 my $last_offset_ok = $offset;
157 0         0 foreach my $l (1 .. $max_length_decoding_key) {
158 0         0 $bitpattern .= vec($bitvector,$offset++,1);
159 0 0       0 if ($decode = $decode_hash{$bitpattern}) {
160 0         0 $string .= $decode;
161 0         0 last;
162             }
163             }
164 0 0       0 defined $decode
165             or die "Unknown bit sequence starting at offset $last_offset_ok in the bitstring";
166             }
167 0         0 return $string;
168             }
169              
170              
171             sub __validate_counting_hash {
172 12     12   15 my $c = shift;
173 12         16 my $error_msg = undef;
174 12 100       69 defined $c
175             or croak "Undefined counting hash";
176 10 100       57 ref($c) eq 'HASH'
177             or croak "The argument for the counting hash is not a hash reference, as expected";
178 8 100       42 scalar(keys %$c) >= 2
179             or croak "The counting hash must have at least 2 keys";
180             }
181              
182             1;
183              
184             package KeyValuePair;
185              
186 4     4   3863 use Heap::Elem;
  4         1034  
  4         741  
187              
188             require Exporter;
189              
190             our @ISA = qw/Exporter Heap::Elem/;
191              
192             sub new {
193 6     6   14 my ($proto, $key, $value) = @_;
194 6   33     30 my $class = ref($proto) || $proto;
195              
196 6         48 my $self = $class->SUPER::new;
197              
198 6         579 $self->{"KeyValuePair::key"} = $key;
199 0           $self->{"KeyValuePair::value"} = $value;
200            
201 0           return $self;
202             }
203              
204             sub cmp {
205 0     0     my ($self, $other) = @_;
206 0           $self->{"KeyValuePair::value"} <=> $other->{"KeyValuePair::value"};
207             }
208              
209             sub key {
210 0     0     my $self = shift;
211 0           return $self->{"KeyValuePair::key"};
212             }
213              
214             sub value {
215 0     0     my $self = shift;
216 0           return $self->{"KeyValuePair::value"};
217             }
218              
219             1;
220              
221              
222             __END__