File Coverage

blib/lib/Hash/Ordered.pm
Criterion Covered Total %
statement 198 201 98.5
branch 71 78 93.5
condition 4 7 57.1
subroutine 43 44 97.7
pod 25 25 100.0
total 341 355 96.6


line stmt bran cond sub pod time code
1 3     3   201491 use 5.006;
  3         35  
2 3     3   13 use strict;
  3         4  
  3         50  
3 3     3   12 use warnings;
  3         4  
  3         146  
4              
5             package Hash::Ordered;
6             # ABSTRACT: A fast, pure-Perl ordered hash class
7              
8             our $VERSION = '0.013'; # TRIAL
9              
10 3     3   15 use Carp ();
  3         3  
  3         130  
11              
12             use constant {
13 3         281 _DATA => 0, # unordered data
14             _KEYS => 1, # ordered keys
15             _INDX => 2, # index into _KEYS (on demand)
16             _OFFS => 3, # index offset for optimized shift/unshift
17             _GCNT => 4, # garbage count
18             _ITER => 5, # for tied hash support
19 3     3   13 };
  3         5  
20              
21             use constant {
22 3         251 _INDEX_THRESHOLD => 25, # max size before indexing/tombstone deletion
23             _TOMBSTONE => \1, # ref to arbitrary scalar
24 3     3   16 };
  3         5  
25              
26             # 'overloading.pm' not available until 5.10.1 so emulate with Scalar::Util
27             BEGIN {
28 3 50   3   14 if ( $] gt '5.010000' ) {
29             ## no critic
30 3     3   197 eval q{
  3     3   23  
  3     1   6  
  3     29   170  
  3         16  
  3         5  
  3         109  
  1         7  
  29         481  
31             sub _stringify { no overloading; "$_[0]" }
32             sub _numify { no overloading; 0+$_[0] }
33             };
34 3 50       170 die $@ if $@; # uncoverable branch true
35             }
36             else {
37             ## no critic
38 0         0 eval q{
39             require Scalar::Util;
40             sub _stringify { sprintf("%s=ARRAY(0x%x)",ref($_[0]),Scalar::Util::refaddr($_[0])) }
41             sub _numify { Scalar::Util::refaddr($_[0]) }
42             };
43 0 0       0 die $@ if $@; # uncoverable branch true
44             }
45             }
46              
47             use overload
48             q{""} => \&_stringify,
49             q{0+} => \&_numify,
50 2     2   342 q{bool} => sub { !!scalar %{ $_[0]->[_DATA] } },
  2         11  
51 3     3   15 fallback => 1;
  3         5  
  3         37  
52              
53             #pod =method new
54             #pod
55             #pod $oh = Hash::Ordered->new;
56             #pod $oh = Hash::Ordered->new( @pairs );
57             #pod
58             #pod Constructs an object, with an optional list of key-value pairs.
59             #pod
60             #pod The position of a key corresponds to the first occurrence in the list, but
61             #pod the value will be updated if the key is seen more than once.
62             #pod
63             #pod Current API available since 0.009.
64             #pod
65             #pod =cut
66              
67             sub new {
68 29     29 1 80467 my $class = shift;
69              
70 29 100       271 Carp::croak("new() requires key-value pairs") unless @_ % 2 == 0;
71              
72 28         53 my ( %data, @keys, $k );
73 28         97 while (@_) {
74             # must stringify keys for _KEYS array
75 3284         3125 $k = shift;
76 3284 100       5556 push @keys, "$k" unless exists $data{$k};
77 3284         5912 $data{$k} = shift;
78             }
79 28         147 return bless [ \%data, \@keys, undef, 0, 0 ], $class;
80             }
81              
82             #pod =method clone
83             #pod
84             #pod $oh2 = $oh->clone;
85             #pod $oh2 = $oh->clone( @keys );
86             #pod
87             #pod Creates a shallow copy of an ordered hash object. If no arguments are
88             #pod given, it produces an exact copy. If a list of keys is given, the new
89             #pod object includes only those keys in the given order. Keys that aren't
90             #pod in the original will have the value C.
91             #pod
92             #pod =cut
93              
94             sub clone {
95 15     15 1 6949 my $self = CORE::shift;
96 15         21 my $clone;
97 15 100       43 if (@_) {
    100          
98 9         12 my %subhash;
99 9         18 @subhash{@_} = @{ $self->[_DATA] }{@_};
  9         659  
100 9         651 $clone = [ \%subhash, [ map "$_", @_ ], undef, 0, 0 ];
101             }
102             elsif ( $self->[_INDX] ) {
103             $clone =
104 3         7 [ { %{ $self->[_DATA] } }, [ grep !ref($_), @{ $self->[_KEYS] } ], undef, 0, 0 ];
  3         979  
  3         500  
105             }
106             else {
107             $clone =
108 3         5 [ { %{ $self->[_DATA] } }, [ @{ $self->[_KEYS] } ], undef, 0, 0 ];
  3         30  
  3         45  
109              
110             }
111 15         201 return bless $clone, ref $self;
112             }
113              
114             #pod =method keys
115             #pod
116             #pod @keys = $oh->keys;
117             #pod $size = $oh->keys;
118             #pod
119             #pod In list context, returns the ordered list of keys. In scalar context, returns
120             #pod the number of elements.
121             #pod
122             #pod Current API available since 0.005.
123             #pod
124             #pod =cut
125              
126             sub keys {
127 27     27 1 30342 my ($self) = @_;
128             return wantarray
129 25         1593 ? ( grep !ref($_), @{ $self->[_KEYS] } )
130 27 100       75 : @{ $self->[_KEYS] } - $self->[_GCNT];
  2         8  
131             }
132              
133             #pod =method values
134             #pod
135             #pod @values = $oh->values;
136             #pod @values = $oh->values( @keys );
137             #pod
138             #pod Returns an ordered list of values. If no arguments are given, returns
139             #pod the ordered values of the entire hash. If a list of keys is given, returns
140             #pod values in order corresponding to those keys. If a key does not exist, C
141             #pod will be returned for that value.
142             #pod
143             #pod In scalar context, returns the number of elements.
144             #pod
145             #pod Current API available since 0.006.
146             #pod
147             #pod =cut
148              
149             sub values {
150 16     16 1 48 my $self = CORE::shift;
151             return
152             wantarray
153 3049         3842 ? ( map { $self->[_DATA]{$_} } ( @_ ? @_ : grep !ref($_), @{ $self->[_KEYS] } ) )
  13         189  
154 16 100       57 : @{ $self->[_KEYS] } - $self->[_GCNT];
  2 100       33  
155             }
156              
157             #pod =method get
158             #pod
159             #pod $value = $oh->get("some key");
160             #pod
161             #pod Returns the value associated with the key, or C if it does not exist in
162             #pod the hash.
163             #pod
164             #pod =cut
165              
166             sub get {
167 1068     1068 1 2700 return $_[0]->[_DATA]{ $_[1] };
168             }
169              
170             #pod =method set
171             #pod
172             #pod $oh->set("some key" => "some value");
173             #pod
174             #pod Associates a value with a key and returns the value. If the key does not
175             #pod already exist in the hash, it will be added at the end.
176             #pod
177             #pod =cut
178              
179             sub set {
180 537     537 1 998 my ( $self, $key ) = @_; # don't copy $_[2] in case it's large
181 537 100       827 if ( !exists $self->[_DATA]{$key} ) {
182 324         380 my $keys = $self->[_KEYS];
183 324 100       426 if ( my $indx = $self->[_INDX] ) {
184 4 50       25 $indx->{$key} = @$keys ? $indx->{ $keys->[-1] } + 1 : 0;
185             }
186 324         311 CORE::push @{ $self->[_KEYS] }, "$key"; # stringify key
  324         558  
187             }
188 537         936 return $self->[_DATA]{$key} = $_[2];
189             }
190              
191             #pod =method exists
192             #pod
193             #pod if ( $oh->exists("some key") ) { ... }
194             #pod
195             #pod Test if some key exists in the hash (without creating it).
196             #pod
197             #pod =cut
198              
199             sub exists {
200 6     6 1 48 return exists $_[0]->[_DATA]{ $_[1] };
201             }
202              
203             #pod =method delete
204             #pod
205             #pod $value = $oh->delete("some key");
206             #pod
207             #pod Removes a key-value pair from the hash and returns the value.
208             #pod
209             #pod =cut
210              
211             sub delete {
212 468     468 1 243258 my ( $self, $key ) = @_;
213 468 100       814 if ( exists $self->[_DATA]{$key} ) {
214 466         506 my $keys = $self->[_KEYS];
215              
216             # JIT an index if hash is "large"
217 466 100 100     930 if ( !$self->[_INDX] && @$keys > _INDEX_THRESHOLD ) {
218 12         22 my %indx;
219 12         17 $indx{ $keys->[$_] } = $_ for 0 .. $#{$keys};
  12         1130  
220 12         38 $self->[_INDX] = \%indx;
221             }
222              
223 466 100       658 if ( $self->[_INDX] ) {
224              
225             # tombstone
226 353         595 $keys->[ delete( $self->[_INDX]{$key} ) + $self->[_OFFS] ] = _TOMBSTONE;
227              
228             # GC keys and remove index if more than half keys are tombstone.
229             # Index will be recreated if needed on next delete
230 353 100       1159 if ( ++$self->[_GCNT] > @$keys / 2 ) {
    100          
    100          
231 4         5 @{ $self->[_KEYS] } = grep !ref($_), @{ $self->[_KEYS] };
  4         22  
  4         21  
232 4         12 $self->[_INDX] = undef;
233 4         8 $self->[_OFFS] = 0;
234 4         5 $self->[_GCNT] = 0;
235             }
236             # or maybe garbage collect start of list
237             elsif ( ref( $keys->[0] ) ) {
238 182         206 my $i = 0;
239 182         367 $i++ while ref( $keys->[$i] );
240 182         207 splice @$keys, 0, $i;
241 182         201 $self->[_GCNT] -= $i;
242 182         196 $self->[_OFFS] -= $i;
243             }
244             # or maybe garbage collect end of list
245             elsif ( ref( $keys->[-1] ) ) {
246 85         96 my $i = $#{$keys};
  85         111  
247 85         178 $i-- while ref( $keys->[$i] );
248 85         94 $self->[_GCNT] -= $#{$keys} - $i;
  85         107  
249 85         126 splice @$keys, $i + 1;
250             }
251             }
252             else {
253 113         117 my $i;
254 113         119 for ( 0 .. $#{$keys} ) {
  113         195  
255 808 100       1249 if ( $keys->[$_] eq $key ) { $i = $_; last; }
  113         126  
  113         161  
256             }
257 113         165 splice @$keys, $i, 1;
258             }
259              
260 466         900 return delete $self->[_DATA]{$key};
261             }
262 2         9 return undef; ## no critic
263             }
264              
265             #pod =method clear
266             #pod
267             #pod $oh->clear;
268             #pod
269             #pod Removes all key-value pairs from the hash. Returns undef in scalar context
270             #pod or an empty list in list context.
271             #pod
272             #pod Current API available since 0.003.
273             #pod
274             #pod =cut
275              
276             sub clear {
277 20     20 1 106308 my ($self) = @_;
278 20         173 @$self = ( {}, [], undef, 0, 0 );
279 20         50 return;
280             }
281              
282             #pod =method push
283             #pod
284             #pod $oh->push( one => 1, two => 2);
285             #pod
286             #pod Add a list of key-value pairs to the end of the ordered hash. If a key already
287             #pod exists in the hash, it will be deleted and re-inserted at the end with the new
288             #pod value.
289             #pod
290             #pod Returns the number of keys after the push is complete.
291             #pod
292             #pod =cut
293              
294             sub push {
295 237     237 1 86987 my $self = CORE::shift;
296 237         327 my ( $data, $keys ) = @$self;
297 237         336 while (@_) {
298 245         371 my ( $k, $v ) = splice( @_, 0, 2 );
299 245 100       522 $self->delete($k) if exists $data->{$k};
300 245         409 $data->{$k} = $v;
301 245 100       364 if ( my $indx = $self->[_INDX] ) {
302 115 50       242 $indx->{$k} = @$keys ? $indx->{ $keys->[-1] } + 1 : 0;
303             }
304 245         514 CORE::push @$keys, "$k"; # stringify keys
305             }
306 237         376 return @$keys - $self->[_GCNT];
307             }
308              
309             #pod =method pop
310             #pod
311             #pod ($key, $value) = $oh->pop;
312             #pod $value = $oh->pop;
313             #pod
314             #pod Removes and returns the last key-value pair in the ordered hash.
315             #pod In scalar context, only the value is returned. If the hash is empty,
316             #pod the returned key and value will be C.
317             #pod
318             #pod =cut
319              
320             sub pop {
321 1028     1028 1 19192 my ($self) = @_;
322 1028 100       1300 if ( $self->[_INDX] ) {
323 2         6 my $key = $self->[_KEYS][-1];
324 2         9 return $key, $self->delete($key);
325             }
326             else {
327 1026         956 my $key = CORE::pop @{ $self->[_KEYS] };
  1026         1199  
328 1026 100       2452 return defined($key) ? ( $key, delete $self->[_DATA]{$key} ) : ();
329             }
330             }
331              
332             #pod =method unshift
333             #pod
334             #pod $oh->unshift( one => 1, two => 2 );
335             #pod
336             #pod Adds a list of key-value pairs to the beginning of the ordered hash. If a key
337             #pod already exists, it will be deleted and re-inserted at the beginning with the
338             #pod new value.
339             #pod
340             #pod Returns the number of keys after the unshift is complete.
341             #pod
342             #pod =cut
343              
344             sub unshift {
345 215     215 1 62778 my $self = CORE::shift;
346 215         280 my ( $data, $keys ) = @$self;
347 215         305 while (@_) {
348 226         322 my ( $k, $v ) = splice( @_, -2, 2 );
349 226 100       437 $self->delete($k) if exists $data->{$k};
350 226         360 $data->{$k} = $v;
351 226         352 CORE::unshift @$keys, "$k"; # stringify keys
352 226 100       467 $self->[_INDX]{$k} = -( ++$self->[_OFFS] ) if $self->[_INDX];
353             }
354 215         326 return @$keys - $self->[_GCNT];
355             }
356              
357             #pod =method shift
358             #pod
359             #pod ($key, $value) = $oh->shift;
360             #pod $value = $oh->shift;
361             #pod
362             #pod Removes and returns the first key-value pair in the ordered hash.
363             #pod In scalar context, only the value is returned. If the hash is empty,
364             #pod the returned key and value will be C.
365             #pod
366             #pod =cut
367              
368             sub shift {
369 1028     1028 1 19272 my ($self) = @_;
370 1028 100       1248 if ( $self->[_INDX] ) {
371 2         7 my $key = $self->[_KEYS][0];
372 2         9 return $key, $self->delete($key);
373             }
374             else {
375 1026         978 my $key = CORE::shift @{ $self->[_KEYS] };
  1026         1269  
376 1026 100       2362 return defined($key) ? ( $key, delete $self->[_DATA]{$key} ) : ();
377             }
378             }
379              
380             #pod =method merge
381             #pod
382             #pod $oh->merge( one => 1, two => 2 );
383             #pod
384             #pod Merges a list of key-value pairs into the ordered hash. If a key already
385             #pod exists, its value is replaced. Otherwise, the key-value pair is added at
386             #pod the end of the hash.
387             #pod
388             #pod =cut
389              
390             sub merge {
391 2     2 1 6 my $self = CORE::shift;
392 2         10 while (@_) {
393 6         13 my ( $k, $v ) = splice( @_, 0, 2 );
394 6 100       20 if ( !exists $self->[_DATA]{$k} ) {
395 4         5 my $size = CORE::push @{ $self->[_KEYS] }, "$k"; # stringify key
  4         12  
396 4 100       14 $self->[_INDX]{$k} = $size - 1 if $self->[_INDX];
397             }
398 6         15 $self->[_DATA]{$k} = $v;
399             }
400 2         5 return @{ $self->[_KEYS] } - $self->[_GCNT];
  2         14  
401             }
402              
403             #pod =method as_list
404             #pod
405             #pod @pairs = $oh->as_list;
406             #pod @pairs = $oh->as_list( @keys );
407             #pod
408             #pod Returns an ordered list of key-value pairs. If no arguments are given, all
409             #pod pairs in the hash are returned. If a list of keys is given, the returned list
410             #pod includes only those key-value pairs in the given order. Keys that aren't in
411             #pod the original will have the value C.
412             #pod
413             #pod =cut
414              
415             sub as_list {
416 34     34 1 918 my $self = CORE::shift;
417             return
418 12180         20134 map { ; $_ => $self->[_DATA]{$_} }
419 34 100       81 ( @_ ? @_ : grep !ref($_), @{ $self->[_KEYS] } );
  33         866  
420             }
421              
422             #pod =method iterator
423             #pod
424             #pod $iter = $oh->iterator;
425             #pod $iter = $oh->iterator( reverse $oh->keys ); # reverse
426             #pod
427             #pod while ( my ($key,$value) = $iter->() ) { ... }
428             #pod
429             #pod Returns a code reference that returns a single key-value pair (in order) on
430             #pod each invocation, or the empty list if all keys are visited.
431             #pod
432             #pod If no arguments are given, the iterator walks the entire hash in order. If a
433             #pod list of keys is provided, the iterator walks the hash in that order. Unknown
434             #pod keys will return C.
435             #pod
436             #pod The list of keys to return is set when the iterator is generator. Keys added
437             #pod later will not be returned. Subsequently deleted keys will return C
438             #pod for the value.
439             #pod
440             #pod =cut
441              
442             # usually we avoid copying keys in @_; here we must for the closure
443             sub iterator {
444 2     2 1 1113 my ( $self, @keys ) = @_;
445 2 100       6 @keys = grep !ref($_), @{ $self->[_KEYS] } unless @keys;
  1         7  
446 2         4 my $data = $self->[_DATA];
447             return sub {
448 28 100   28   100 return unless @keys;
449 26         32 my $key = CORE::shift(@keys);
450 26         51 return ( $key => $data->{$key} );
451 2         12 };
452             }
453              
454             #pod =method preinc
455             #pod
456             #pod $oh->preinc($key); # like ++$hash{$key}
457             #pod
458             #pod This method is sugar for incrementing a key without having to call C and
459             #pod C explicitly. It returns the new value.
460             #pod
461             #pod Current API available since 0.005.
462             #pod
463             #pod =cut
464              
465             sub preinc {
466 1     1 1 346 return ++$_[0]->[_DATA]{ $_[1] };
467             }
468              
469             #pod =method postinc
470             #pod
471             #pod $oh->postinc($key); # like $hash{$key}++
472             #pod
473             #pod This method is sugar for incrementing a key without having to call C and
474             #pod C explicitly. It returns the old value.
475             #pod
476             #pod Current API available since 0.005.
477             #pod
478             #pod =cut
479              
480             sub postinc {
481 1     1 1 5 return $_[0]->[_DATA]{ $_[1] }++;
482             }
483              
484             #pod =method predec
485             #pod
486             #pod $oh->predec($key); # like --$hash{$key}
487             #pod
488             #pod This method is sugar for decrementing a key without having to call C and
489             #pod C explicitly. It returns the new value.
490             #pod
491             #pod Current API available since 0.005.
492             #pod
493             #pod =cut
494              
495             sub predec {
496 1     1 1 4 return --$_[0]->[_DATA]{ $_[1] };
497             }
498              
499             #pod =method postdec
500             #pod
501             #pod $oh->postdec($key); # like $hash{$key}--
502             #pod
503             #pod This method is sugar for decrementing a key without having to call C and
504             #pod C explicitly. It returns the old value.
505             #pod
506             #pod Current API available since 0.005.
507             #pod
508             #pod =cut
509              
510             sub postdec {
511 1     1 1 28 return $_[0]->[_DATA]{ $_[1] }--;
512             }
513              
514             #pod =method add
515             #pod
516             #pod $oh->add($key, $n); # like $hash{$key} += $n
517             #pod
518             #pod This method is sugar for adding a value to a key without having to call
519             #pod C and C explicitly. With no value to add, it is treated as "0".
520             #pod It returns the new value.
521             #pod
522             #pod Current API available since 0.005.
523             #pod
524             #pod =cut
525              
526             sub add {
527 2   50 2 1 12 return $_[0]->[_DATA]{ $_[1] } += $_[2] || 0;
528             }
529              
530             #pod =method subtract
531             #pod
532             #pod $oh->subtract($key, $n); # like $hash{$key} -= $n
533             #pod
534             #pod This method is sugar for subtracting a value from a key without having to call
535             #pod C and C explicitly. With no value to subtract, it is treated as "0".
536             #pod It returns the new value.
537             #pod
538             #pod Current API available since 0.005.
539             #pod
540             #pod =cut
541              
542             sub subtract {
543 0   0 0 1 0 return $_[0]->[_DATA]{ $_[1] } -= $_[2] || 0;
544             }
545              
546             #pod =method concat
547             #pod
548             #pod $oh->concat($key, $str); # like $hash{$key} .= $str
549             #pod
550             #pod This method is sugar for concatenating a string onto the value of a key without
551             #pod having to call C and C explicitly. It returns the new value. If the
552             #pod value to append is not defined, no concatenation is done and no warning is
553             #pod given.
554             #pod
555             #pod Current API available since 0.005.
556             #pod
557             #pod =cut
558              
559             sub concat {
560 2 100   2 1 7 if ( defined $_[2] ) {
561 1         5 return $_[0]->[_DATA]{ $_[1] } .= $_[2];
562             }
563             else {
564 1         5 return $_[0]->[_DATA]{ $_[1] };
565             }
566             }
567              
568             #pod =method or_equals
569             #pod
570             #pod $oh->or_equals($key, $str); # like $hash{$key} ||= $str
571             #pod
572             #pod This method is sugar for assigning to a key if the existing value is false
573             #pod without having to call C and C explicitly. It returns the new value.
574             #pod
575             #pod Current API available since 0.005.
576             #pod
577             #pod =cut
578              
579             sub or_equals {
580 5     5 1 523 my ($self,$key) = @_;
581              
582 5 100       9 if ( my $val = $self->get($key) ) {
583 2         6 return $val;
584             }
585              
586 3         9 return $self->set($key,$_[2]);
587             }
588              
589             #pod =method dor_equals
590             #pod
591             #pod $oh->dor_equals($key, $str); # like $hash{$key} //= $str
592             #pod
593             #pod This method is sugar for assigning to a key if the existing value is not
594             #pod defined without having to call C and C explicitly. It returns the new
595             #pod value.
596             #pod
597             #pod Current API available since 0.005.
598             #pod
599             #pod =cut
600              
601             sub dor_equals {
602 5     5 1 555 my ($self,$key) = @_;
603              
604 5 100       13 if ( defined( my $val = $self->get($key) ) ) {
605 3         10 return $val;
606             }
607              
608 2         7 return $self->set($key,$_[2]);
609             }
610              
611             #--------------------------------------------------------------------------#
612             # tied hash support -- slower, but I maybe some thing are more succinct
613             #--------------------------------------------------------------------------#
614              
615             {
616 3     3   5724 no strict 'refs';
  3         6  
  3         800  
617              
618             *{ __PACKAGE__ . '::TIEHASH' } = \&new;
619             *{ __PACKAGE__ . '::STORE' } = \&set;
620             *{ __PACKAGE__ . '::FETCH' } = \&get;
621             *{ __PACKAGE__ . '::EXISTS' } = \&exists;
622             *{ __PACKAGE__ . '::DELETE' } = \&delete;
623             *{ __PACKAGE__ . '::CLEAR' } = \&clear;
624             }
625              
626             sub FIRSTKEY {
627 3     3   11 my ($self) = @_;
628 3         5 my @keys = grep !ref($_), @{ $self->[_KEYS] };
  3         21  
629             $self->[_ITER] = sub {
630 44 100   44   69 return unless @keys;
631 41         95 return CORE::shift(@keys);
632 3         21 };
633 3         6 return $self->[_ITER]->();
634             }
635              
636             sub NEXTKEY {
637 41 50   41   76 return defined( $_[0]->[_ITER] ) ? $_[0]->[_ITER]->() : undef;
638             }
639              
640             sub SCALAR {
641 2     2   756 return scalar %{ $_[0]->[_DATA] };
  2         38  
642             }
643              
644             1;
645              
646              
647             # vim: ts=4 sts=4 sw=4 et:
648              
649             __END__