File Coverage

blib/lib/Tie/IxHash.pm
Criterion Covered Total %
statement 162 226 71.6
branch 40 60 66.6
condition 2 6 33.3
subroutine 23 31 74.1
pod 15 20 75.0
total 242 343 70.5


line stmt bran cond sub pod time code
1             #
2             # Tie/IxHash.pm
3             #
4             # Indexed hash implementation for Perl
5             #
6             # See below for documentation.
7             #
8            
9             require 5.005;
10            
11             package Tie::IxHash;
12 2     2   30035 use strict;
  2         4  
  2         78  
13 2     2   2677 use integer;
  2         23  
  2         11  
14             require Tie::Hash;
15 2     2   87 use vars qw/@ISA $VERSION/;
  2         9  
  2         6471  
16             @ISA = qw(Tie::Hash);
17            
18             $VERSION = $VERSION = '1.23';
19            
20             #
21             # standard tie functions
22             #
23            
24             sub TIEHASH {
25 2     2   34 my($c) = shift;
26 2         5 my($s) = [];
27 2         5 $s->[0] = {}; # hashkey index
28 2         5 $s->[1] = []; # array of keys
29 2         5 $s->[2] = []; # array of data
30 2         5 $s->[3] = 0; # iter count
31            
32 2         5 bless $s, $c;
33            
34 2 100       11 $s->Push(@_) if @_;
35            
36 2         9 return $s;
37             }
38            
39             #sub DESTROY {} # costly if there's nothing to do
40            
41             sub FETCH {
42 48     48   79 my($s, $k) = (shift, shift);
43 48 50       222 return exists( $s->[0]{$k} ) ? $s->[2][ $s->[0]{$k} ] : undef;
44             }
45            
46             sub STORE {
47 20     20   74 my($s, $k, $v) = (shift, shift, shift);
48            
49 20 100       60 if (exists $s->[0]{$k}) {
50 2         5 my($i) = $s->[0]{$k};
51 2         4 $s->[1][$i] = $k;
52 2         3 $s->[2][$i] = $v;
53 2         8 $s->[0]{$k} = $i;
54             }
55             else {
56 18         21 push(@{$s->[1]}, $k);
  18         35  
57 18         19 push(@{$s->[2]}, $v);
  18         31  
58 18         21 $s->[0]{$k} = $#{$s->[1]};
  18         75  
59             }
60             }
61            
62             sub DELETE {
63 11     11   55 my($s, $k) = (shift, shift);
64            
65 11 50       30 if (exists $s->[0]{$k}) {
66 11         19 my($i) = $s->[0]{$k};
67 11         20 for ($i+1..$#{$s->[1]}) { # reset higher elt indexes
  11         31  
68 16         45 $s->[0]{ $s->[1][$_] }--; # timeconsuming, is there is better way?
69             }
70 11 100       32 if ( $i == $s->[3]-1 ) {
71 4         7 $s->[3]--;
72             }
73 11         21 delete $s->[0]{$k};
74 11         11 splice @{$s->[1]}, $i, 1;
  11         20  
75 11         16 return (splice(@{$s->[2]}, $i, 1))[0];
  11         58  
76             }
77 0         0 return undef;
78             }
79            
80             sub EXISTS {
81 0     0   0 exists $_[0]->[0]{ $_[1] };
82             }
83            
84             sub FIRSTKEY {
85 18     18   73 $_[0][3] = 0;
86 18         34 &NEXTKEY;
87             }
88            
89             sub NEXTKEY {
90 74 100   74   126 return $_[0][1][ $_[0][3]++ ] if ($_[0][3] <= $#{ $_[0][1] } );
  74         351  
91 18         75 return undef;
92             }
93            
94            
95            
96             #
97             #
98             # class functions that provide additional capabilities
99             #
100             #
101            
102 0     0 0 0 sub new { TIEHASH(@_) }
103            
104             sub Clear {
105 1     1 1 2 my $s = shift;
106 1         3 $s->[0] = {}; # hashkey index
107 1         4 $s->[1] = []; # array of keys
108 1         2 $s->[2] = []; # array of data
109 1         3 $s->[3] = 0; # iter count
110 1         2 return;
111             }
112            
113             #
114             # add pairs to end of indexed hash
115             # note that if a supplied key exists, it will not be reordered
116             #
117             sub Push {
118 5     5 1 18 my($s) = shift;
119 5         13 while (@_) {
120 10         21 $s->STORE(shift, shift);
121             }
122 5         6 return scalar(@{$s->[1]});
  5         11  
123             }
124            
125             sub Push2 {
126 0     0 0 0 my($s) = shift;
127 0         0 $s->Splice($#{$s->[1]}+1, 0, @_);
  0         0  
128 0         0 return scalar(@{$s->[1]});
  0         0  
129             }
130            
131             #
132             # pop last k-v pair
133             #
134             sub Pop {
135 3     3 1 6 my($s) = shift;
136 3         4 my($k, $v, $i);
137 3         4 $k = pop(@{$s->[1]});
  3         8  
138 3         5 $v = pop(@{$s->[2]});
  3         6  
139 3 50       18 if (defined $k) {
140 3         40 delete $s->[0]{$k};
141 3         13 return ($k, $v);
142             }
143 0         0 return undef;
144             }
145            
146             sub Pop2 {
147 0     0 0 0 return $_[0]->Splice(-1);
148             }
149            
150             #
151             # shift
152             #
153             sub Shift {
154 0     0 1 0 my($s) = shift;
155 0         0 my($k, $v, $i);
156 0         0 $k = shift(@{$s->[1]});
  0         0  
157 0         0 $v = shift(@{$s->[2]});
  0         0  
158 0 0       0 if (defined $k) {
159 0         0 delete $s->[0]{$k};
160 0         0 for (keys %{$s->[0]}) {
  0         0  
161 0         0 $s->[0]{$_}--;
162             }
163 0         0 return ($k, $v);
164             }
165 0         0 return undef;
166             }
167            
168             sub Shift2 {
169 0     0 0 0 return $_[0]->Splice(0, 1);
170             }
171            
172             #
173             # unshift
174             # if a supplied key exists, it will not be reordered
175             #
176             sub Unshift {
177 0     0 1 0 my($s) = shift;
178 0         0 my($k, $v, @k, @v, $len, $i);
179            
180 0         0 while (@_) {
181 0         0 ($k, $v) = (shift, shift);
182 0 0       0 if (exists $s->[0]{$k}) {
183 0         0 $i = $s->[0]{$k};
184 0         0 $s->[1][$i] = $k;
185 0         0 $s->[2][$i] = $v;
186 0         0 $s->[0]{$k} = $i;
187             }
188             else {
189 0         0 push(@k, $k);
190 0         0 push(@v, $v);
191 0         0 $len++;
192             }
193             }
194 0 0       0 if (defined $len) {
195 0         0 for (keys %{$s->[0]}) {
  0         0  
196 0         0 $s->[0]{$_} += $len;
197             }
198 0         0 $i = 0;
199 0         0 for (@k) {
200 0         0 $s->[0]{$_} = $i++;
201             }
202 0         0 unshift(@{$s->[1]}, @k);
  0         0  
203 0         0 return unshift(@{$s->[2]}, @v);
  0         0  
204             }
205 0         0 return scalar(@{$s->[1]});
  0         0  
206             }
207            
208             sub Unshift2 {
209 0     0 0 0 my($s) = shift;
210 0         0 $s->Splice(0,0,@_);
211 0         0 return scalar(@{$s->[1]});
  0         0  
212             }
213            
214             #
215             # splice
216             #
217             # any existing hash key order is preserved. the value is replaced for
218             # such keys, and the new keys are spliced in the regular fashion.
219             #
220             # supports -ve offsets but only +ve lengths
221             #
222             # always assumes a 0 start offset
223             #
224             sub Splice {
225 2     2 1 4 my($s, $start, $len) = (shift, shift, shift);
226 2         3 my($k, $v, @k, @v, @r, $i, $siz);
227 0         0 my($end); # inclusive
228            
229             # XXX inline this
230 2         7 ($start, $end, $len) = $s->_lrange($start, $len);
231            
232 2 50       6 if (defined $start) {
233 2 100       5 if ($len > 0) {
234 1         3 my(@k) = splice(@{$s->[1]}, $start, $len);
  1         3  
235 1         3 my(@v) = splice(@{$s->[2]}, $start, $len);
  1         3  
236 1         5 while (@k) {
237 2         3 $k = shift(@k);
238 2         5 delete $s->[0]{$k};
239 2         8 push(@r, $k, shift(@v));
240             }
241 1         2 for ($start..$#{$s->[1]}) {
  1         5  
242 0         0 $s->[0]{$s->[1][$_]} -= $len;
243             }
244             }
245 2         7 while (@_) {
246 3         7 ($k, $v) = (shift, shift);
247 3 50       8 if (exists $s->[0]{$k}) {
248             # $s->STORE($k, $v);
249 0         0 $i = $s->[0]{$k};
250 0         0 $s->[1][$i] = $k;
251 0         0 $s->[2][$i] = $v;
252 0         0 $s->[0]{$k} = $i;
253             }
254             else {
255 3         5 push(@k, $k);
256 3         6 push(@v, $v);
257 3         7 $siz++;
258             }
259             }
260 2 50       7 if (defined $siz) {
261 2         2 for ($start..$#{$s->[1]}) {
  2         14  
262 0         0 $s->[0]{$s->[1][$_]} += $siz;
263             }
264 2         11 $i = $start;
265 2         5 for (@k) {
266 3         11 $s->[0]{$_} = $i++;
267             }
268 2         4 splice(@{$s->[1]}, $start, 0, @k);
  2         5  
269 2         5 splice(@{$s->[2]}, $start, 0, @v);
  2         5  
270             }
271             }
272 2         9 return @r;
273             }
274            
275             #
276             # delete elements specified by key
277             # other elements higher than the one deleted "slide" down
278             #
279             sub Delete {
280 1     1 1 2 my($s) = shift;
281            
282 1         3 for (@_) {
283             #
284             # XXX potential optimization: could do $s->DELETE only if $#_ < 4.
285             # otherwise, should reset all the hash indices in one loop
286             #
287 2         12 $s->DELETE($_);
288             }
289             }
290            
291             #
292             # replace hash element at specified index
293             #
294             # if the optional key is not supplied the value at index will simply be
295             # replaced without affecting the order.
296             #
297             # if an element with the supplied key already exists, it will be deleted first.
298             #
299             # returns the key of replaced value if it succeeds.
300             #
301             sub Replace {
302 2     2 1 4 my($s) = shift;
303 2         4 my($i, $v, $k) = (shift, shift, shift);
304 2 50 33     9 if (defined $i and $i <= $#{$s->[1]} and $i >= 0) {
  2   33     13  
305 2 100       7 if (defined $k) {
306 1         3 delete $s->[0]{ $s->[1][$i] };
307 1         3 $s->DELETE($k) ; #if exists $s->[0]{$k};
308 1         2 $s->[1][$i] = $k;
309 1         2 $s->[2][$i] = $v;
310 1         2 $s->[0]{$k} = $i;
311 1         3 return $k;
312             }
313             else {
314 1         3 $s->[2][$i] = $v;
315 1         3 return $s->[1][$i];
316             }
317             }
318 0         0 return undef;
319             }
320            
321             #
322             # Given an $start and $len, returns a legal start and end (where start <= end)
323             # for the current hash.
324             # Legal range is defined as 0 to $#s+1
325             # $len defaults to number of elts upto end of list
326             #
327             # 0 1 2 ...
328             # | X | X | X ... X | X | X |
329             # -2 -1 (no -0 alas)
330             # X's above are the elements
331             #
332             sub _lrange {
333 2     2   4 my($s) = shift;
334 2         4 my($offset, $len) = @_;
335 2         4 my($start, $end); # both inclusive
336 2         3 my($size) = $#{$s->[1]}+1;
  2         5  
337            
338 2 50       7 return undef unless defined $offset;
339 2 50       11 if($offset < 0) {
340 0         0 $start = $offset + $size;
341 0 0       0 $start = 0 if $start < 0;
342             }
343             else {
344 2 100       7 ($offset > $size) ? ($start = $size) : ($start = $offset);
345             }
346            
347 2 50       5 if (defined $len) {
348 2 50       5 $len = -$len if $len < 0;
349 2 100       7 $len = $size - $start if $len > $size - $start;
350             }
351             else {
352 0         0 $len = $size - $start;
353             }
354 2         3 $end = $start + $len - 1;
355            
356 2         6 return ($start, $end, $len);
357             }
358            
359             #
360             # Return keys at supplied indices
361             # Returns all keys if no args.
362             #
363             sub Keys {
364 7     7 1 13 my($s) = shift;
365 1         6 return ( @_ == 1
366             ? $s->[1][$_[0]]
367             : ( @_
368 5         32 ? @{$s->[1]}[@_]
369 7 100       21 : @{$s->[1]} ) );
    100          
370             }
371            
372             #
373             # Returns values at supplied indices
374             # Returns all values if no args.
375             #
376             sub Values {
377 3     3 1 6 my($s) = shift;
378 1         6 return ( @_ == 1
379             ? $s->[2][$_[0]]
380             : ( @_
381 1         6 ? @{$s->[2]}[@_]
382 3 100       11 : @{$s->[2]} ) );
    100          
383             }
384            
385             #
386             # get indices of specified hash keys
387             #
388             sub Indices {
389 2     2 1 4 my($s) = shift;
390 2 100       8 return ( @_ == 1 ? $s->[0]{$_[0]} : @{$s->[0]}{@_} );
  1         7  
391             }
392            
393             #
394             # number of k-v pairs in the ixhash
395             # note that this does not equal the highest index
396             # owing to preextended arrays
397             #
398             sub Length {
399 2     2 1 4 return scalar @{$_[0]->[1]};
  2         8  
400             }
401            
402             #
403             # Reorder the hash in the supplied key order
404             #
405             # warning: any unsupplied keys will be lost from the hash
406             # any supplied keys that dont exist in the hash will be ignored
407             #
408             sub Reorder {
409 4     4 1 7 my($s) = shift;
410 4         6 my(@k, @v, %x, $i);
411 4 50       10 return unless @_;
412            
413 4         7 $i = 0;
414 4         9 for (@_) {
415 14 100       33 if (exists $s->[0]{$_}) {
416 13         20 push(@k, $_);
417 13         34 push(@v, $s->[2][ $s->[0]{$_} ] );
418 13         41 $x{$_} = $i++;
419             }
420             }
421 4         8 $s->[1] = \@k;
422 4         7 $s->[2] = \@v;
423 4         9 $s->[0] = \%x;
424 4         16 return $s;
425             }
426            
427             sub SortByKey {
428 1     1 1 2 my($s) = shift;
429 1         3 $s->Reorder(sort $s->Keys);
430             }
431            
432             sub SortByValue {
433 1     1 1 3 my($s) = shift;
434 1         3 $s->Reorder(sort { $s->FETCH($a) cmp $s->FETCH($b) } $s->Keys)
  5         10  
435             }
436            
437             1;
438             __END__