File Coverage

blib/lib/Hash/Ordered.pm
Criterion Covered Total %
statement 195 200 97.5
branch 69 80 91.2
condition 9 13 69.2
subroutine 44 45 97.7
pod 25 25 100.0
total 342 363 95.3


line stmt bran cond sub pod time code
1 3     3   107249 use 5.006;
  3         8  
2 3     3   9 use strict;
  3         2  
  3         45  
3 3     3   9 use warnings;
  3         3  
  3         138  
4              
5             package Hash::Ordered;
6             # ABSTRACT: A fast, pure-Perl ordered hash class
7              
8             our $VERSION = '0.012';
9              
10 3     3   9 use Carp ();
  3         3  
  3         68  
11              
12             use constant {
13 3         283 _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   12 };
  3         3  
20              
21             use constant {
22 3         250 _INDEX_THRESHOLD => 25, # max size before indexing/tombstone deletion
23             _TOMBSTONE => \1, # ref to arbitrary scalar
24 3     3   13 };
  3         4  
25              
26             # 'overloading.pm' not available until 5.10.1 so emulate with Scalar::Util
27             BEGIN {
28 3 50   3   11 if ( $] gt '5.010000' ) {
29             ## no critic
30 3     3   182 eval q{
  3     3   14  
  3     1   1  
  3     29   168  
  3         12  
  3         1  
  3         93  
  1         6  
  29         353  
31             sub _stringify { no overloading; "$_[0]" }
32             sub _numify { no overloading; 0+$_[0] }
33             };
34 3 50       161 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   216 q{bool} => sub { !!scalar %{ $_[0]->[_DATA] } },
  2         9  
51 3     3   28 fallback => 1;
  3         4  
  3         28  
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 28     28 1 48504 my $class = shift;
69              
70 28 100       270 Carp::croak("new() requires key-value pairs") unless @_ % 2 == 0;
71              
72 27         35 my ( %data, @keys, $k );
73 27         73 while (@_) {
74             # must stringify keys for _KEYS array
75 3284         1896 $k = shift;
76 3284 100       4414 push @keys, "$k" unless exists $data{$k};
77 3284         4976 $data{$k} = shift;
78             }
79 27         132 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 4689 my $self = CORE::shift;
96 15         16 my $clone;
97 15 100       44 if (@_) {
    100          
98 9         13 my %subhash;
99 9         19 @subhash{@_} = @{ $self->[_DATA] }{@_};
  9         644  
100 9         645 $clone = [ \%subhash, [ map "$_", @_ ], undef, 0, 0 ];
101             }
102             elsif ( $self->[_INDX] ) {
103             $clone =
104 3         5 [ { %{ $self->[_DATA] } }, [ grep !ref($_), @{ $self->[_KEYS] } ], undef, 0, 0 ];
  3         1019  
  3         537  
105             }
106             else {
107             $clone =
108 3         4 [ { %{ $self->[_DATA] } }, [ @{ $self->[_KEYS] } ], undef, 0, 0 ];
  3         24  
  3         15  
109              
110             }
111 15         194 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 22     22 1 19869 my ($self) = @_;
128             return wantarray
129 20         1700 ? ( grep !ref($_), @{ $self->[_KEYS] } )
130 22 100       55 : @{ $self->[_KEYS] } - $self->[_GCNT];
  2         6  
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 12     12 1 60 my $self = CORE::shift;
151             return
152             wantarray
153 3040         2495 ? ( map { $self->[_DATA]{$_} } ( @_ ? @_ : grep !ref($_), @{ $self->[_KEYS] } ) )
  9         183  
154 12 100       48 : @{ $self->[_KEYS] } - $self->[_GCNT];
  2 100       7  
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 1058     1058 1 1993 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 531     531 1 829 my ( $self, $key ) = @_; # don't copy $_[2] in case it's large
181 531 100       770 if ( !exists $self->[_DATA]{$key} ) {
182 319         207 my $keys = $self->[_KEYS];
183 319 100       370 if ( my $indx = $self->[_INDX] ) {
184 4 50       30 $indx->{$key} = @$keys ? $indx->{ $keys->[-1] } + 1 : 0;
185             }
186 319         219 CORE::push @{ $self->[_KEYS] }, "$key"; # stringify key
  319         395  
187             }
188 531         727 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 51 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 467     467 1 173766 my ( $self, $key ) = @_;
213 467 100       845 if ( exists $self->[_DATA]{$key} ) {
214 465         350 my $keys = $self->[_KEYS];
215              
216             # JIT an index if hash is "large"
217 465 100 100     1043 if ( !$self->[_INDX] && @$keys > _INDEX_THRESHOLD ) {
218 12         20 my %indx;
219 12         21 $indx{ $keys->[$_] } = $_ for 0 .. $#{$keys};
  12         1123  
220 12         27 $self->[_INDX] = \%indx;
221             }
222              
223 465 100       565 if ( $self->[_INDX] ) {
224              
225             # tombstone
226 354         468 $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 354 100       916 if ( ++$self->[_GCNT] > @$keys / 2 ) {
    100          
    100          
231 4         5 @{ $self->[_KEYS] } = grep !ref($_), @{ $self->[_KEYS] };
  4         21  
  4         22  
232 4         7 $self->[_INDX] = undef;
233 4         11 $self->[_OFFS] = 0;
234 4         30 $self->[_GCNT] = 0;
235             }
236             # or maybe garbage collect start of list
237             elsif ( ref( $keys->[0] ) ) {
238 182         133 my $i = 0;
239 182         362 $i++ while ref( $keys->[$i] );
240 182         134 splice @$keys, 0, $i;
241 182         153 $self->[_GCNT] -= $i;
242 182         128 $self->[_OFFS] -= $i;
243             }
244             # or maybe garbage collect end of list
245             elsif ( ref( $keys->[-1] ) ) {
246 83         63 my $i = $#{$keys};
  83         104  
247 83         190 $i-- while ref( $keys->[$i] );
248 83         64 $self->[_GCNT] -= $#{$keys} - $i;
  83         79  
249 83         91 splice @$keys, $i + 1;
250             }
251             }
252             else {
253 111         78 my $i;
254 111         98 for ( 0 .. $#{$keys} ) {
  111         169  
255 798 100       1088 if ( $keys->[$_] eq $key ) { $i = $_; last; }
  111         76  
  111         94  
256             }
257 111         130 splice @$keys, $i, 1;
258             }
259              
260 465         708 return delete $self->[_DATA]{$key};
261             }
262 2         7 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 71892 my ($self) = @_;
278 20         197 @$self = ( {}, [], undef, 0, 0 );
279 20         42 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 236     236 1 60897 my $self = CORE::shift;
296 236         200 my ( $data, $keys ) = @$self;
297 236         309 while (@_) {
298 244         256 my ( $k, $v ) = splice( @_, 0, 2 );
299 244 100       472 $self->delete($k) if exists $data->{$k};
300 244         329 $data->{$k} = $v;
301 244 100       326 if ( my $indx = $self->[_INDX] ) {
302 115 50       218 $indx->{$k} = @$keys ? $indx->{ $keys->[-1] } + 1 : 0;
303             }
304 244         453 CORE::push @$keys, "$k"; # stringify keys
305             }
306 236         278 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 13575 my ($self) = @_;
322 1028 100       915 if ( $self->[_INDX] ) {
323 2         6 my $key = $self->[_KEYS][-1];
324 2         35 return $key, $self->delete($key);
325             }
326             else {
327 1026         555 my $key = CORE::pop @{ $self->[_KEYS] };
  1026         831  
328 1026 100       1790 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 46892 my $self = CORE::shift;
346 215         210 my ( $data, $keys ) = @$self;
347 215         295 while (@_) {
348 226         247 my ( $k, $v ) = splice( @_, -2, 2 );
349 226 100       393 $self->delete($k) if exists $data->{$k};
350 226         326 $data->{$k} = $v;
351 226         284 CORE::unshift @$keys, "$k"; # stringify keys
352 226 100       485 $self->[_INDX]{$k} = -( ++$self->[_OFFS] ) if $self->[_INDX];
353             }
354 215         278 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 13195 my ($self) = @_;
370 1028 100       901 if ( $self->[_INDX] ) {
371 2         8 my $key = $self->[_KEYS][0];
372 2         8 return $key, $self->delete($key);
373             }
374             else {
375 1026         561 my $key = CORE::shift @{ $self->[_KEYS] };
  1026         1147  
376 1026 100       1802 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 4 my $self = CORE::shift;
392 2         9 while (@_) {
393 6         11 my ( $k, $v ) = splice( @_, 0, 2 );
394 6 100       15 if ( !exists $self->[_DATA]{$k} ) {
395 4         3 my $size = CORE::push @{ $self->[_KEYS] }, "$k"; # stringify key
  4         10  
396 4 100       12 $self->[_INDX]{$k} = $size - 1 if $self->[_INDX];
397             }
398 6         13 $self->[_DATA]{$k} = $v;
399             }
400 2         4 return @{ $self->[_KEYS] } - $self->[_GCNT];
  2         12  
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 641 my $self = CORE::shift;
417             return
418 12180         13833 map { ; $_ => $self->[_DATA]{$_} }
419 34 100       63 ( @_ ? @_ : grep !ref($_), @{ $self->[_KEYS] } );
  33         882  
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 710 my ( $self, @keys ) = @_;
445 2 100       7 @keys = grep !ref($_), @{ $self->[_KEYS] } unless @keys;
  1         7  
446 2         4 my $data = $self->[_DATA];
447             return sub {
448 28 100   28   84 return unless @keys;
449 26         20 my $key = CORE::shift(@keys);
450 26         34 return ( $key => $data->{$key} );
451 2         7 };
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 215 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 6 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 5 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 4 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 11 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 5 if ( defined $_[2] ) {
561 1         6 return $_[0]->[_DATA]{ $_[1] } .= $_[2];
562             }
563             else {
564 1         4 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 3   100 3 1 22 return $_[0]->[_DATA]{ $_[1] } ||= $_[2];
581             }
582              
583             #pod =method dor_equals
584             #pod
585             #pod $oh->dor_equals($key, $str); # like $hash{$key} //= $str
586             #pod
587             #pod This method is sugar for assigning to a key if the existing value is not
588             #pod defined without having to call C and C explicitly. It returns the new
589             #pod value.
590             #pod
591             #pod Current API available since 0.005.
592             #pod
593             #pod =cut
594              
595             BEGIN {
596 3 50   3   5580 if ( $] ge '5.010' ) {
597             ## no critic
598 3   66 3 1 153 eval q{
  3         20  
599             sub dor_equals {
600             return $_[0]->[_DATA]{$_[1]} //= $_[2];
601             }
602             };
603 3 50       74 die $@ if $@; # uncoverable branch true
604             }
605             else {
606             ## no critic
607 0         0 eval q{
608             sub dor_equals {
609             if ( defined $_[0]->[_DATA]{$_[1]} ) {
610             return $_[0]->[_DATA]{$_[1]};
611             }
612             else {
613             return $_[0]->[_DATA]{$_[1]} = $_[2];
614             }
615             }
616             };
617 0 0       0 die $@ if $@; # uncoverable branch true
618             }
619             }
620              
621             #--------------------------------------------------------------------------#
622             # tied hash support -- slower, but I maybe some thing are more succinct
623             #--------------------------------------------------------------------------#
624              
625             {
626 3     3   14 no strict 'refs';
  3         3  
  3         628  
627              
628             *{ __PACKAGE__ . '::TIEHASH' } = \&new;
629             *{ __PACKAGE__ . '::STORE' } = \&set;
630             *{ __PACKAGE__ . '::FETCH' } = \&get;
631             *{ __PACKAGE__ . '::EXISTS' } = \&exists;
632             *{ __PACKAGE__ . '::DELETE' } = \&delete;
633             *{ __PACKAGE__ . '::CLEAR' } = \&clear;
634             }
635              
636             sub FIRSTKEY {
637 3     3   9 my ($self) = @_;
638 3         4 my @keys = grep !ref($_), @{ $self->[_KEYS] };
  3         20  
639             $self->[_ITER] = sub {
640 44 100   44   60 return unless @keys;
641 41         66 return CORE::shift(@keys);
642 3         11 };
643 3         9 return $self->[_ITER]->();
644             }
645              
646             sub NEXTKEY {
647 41 50   41   55 return defined( $_[0]->[_ITER] ) ? $_[0]->[_ITER]->() : undef;
648             }
649              
650             sub SCALAR {
651 2     2   552 return scalar %{ $_[0]->[_DATA] };
  2         44  
652             }
653              
654             1;
655              
656              
657             # vim: ts=4 sts=4 sw=4 et:
658              
659             __END__