File Coverage

blib/lib/Tie/StoredOrderHash.pm
Criterion Covered Total %
statement 82 89 92.1
branch 37 42 88.1
condition 13 21 61.9
subroutine 21 22 95.4
pod 0 11 0.0
total 153 185 82.7


line stmt bran cond sub pod time code
1             package Tie::StoredOrderHash;
2              
3 1     1   37010 use strict;
  1         3  
  1         56  
4 1     1   6 use warnings;
  1         2  
  1         252  
5              
6             our $VERSION = '0.22';
7              
8             sub new {
9             # Return a reference to a new tied hash
10 7     7 0 462 my $class = shift;
11 7         11 my %hash;
12              
13 7 100 66     50 if (@_ == 1 && ref $_[0] eq 'ARRAY') {
14 6         7 tie %hash, $class, @{$_[0]};
  6         36  
15 6         28 return \%hash;
16             }
17             else {
18 1         8 tie %hash, $class, @_;
19 1         4 return \%hash;
20             }
21             }
22              
23             sub ordered($) {
24             # Convenience wrapper around new [ ... ], exported by request
25 6     6 0 2114 return __PACKAGE__->new(@_);
26             }
27              
28             sub is_ordered {
29             # Return true if the argument is a reference to a hash tied to a T::SOH.
30 2     2 0 8 return eval {
31 2         4 (tied %{shift()})->isa(__PACKAGE__)
  2         30  
32             }
33             }
34              
35 1     1   13 use Exporter 'import';
  1         7  
  1         1410  
36             our @EXPORT_OK = qw/ ordered is_ordered /;
37              
38              
39             # T::SOH object is implemented as hash, pointing into an linked-list;
40             # the object itself is a blessed arrayref.
41             # There are four entries in the array ...
42 224     224 0 606 sub lookup { 0 }
43 199     199 0 470 sub first_link { 1 }
44 99     99 0 249 sub last_link { 2 }
45 349     349 0 507 sub iter_link { 3 }
46              
47             # Each entry in the list contains the (key, value) pair
48             # as well as the familiar (prev, next) links
49 193     193 0 907 sub key { 0 }
50 100     100 0 445 sub value { 1 }
51 90     90 0 194 sub prev_link { 2 }
52 313     313 0 1319 sub next_link { 3 }
53              
54             # Note that these subs above are just the same as 'use constant'ing, but ...
55             # you know ...
56             # some people are superstitious about how long it takes to load that module.
57              
58              
59             sub TIEHASH {
60 14     14   4054 my $class = shift;
61              
62 14 100       39 if (@_) {
63             # Construct doubly-linked list for (key, value) pairs
64             # The list entry containing "key" is stored as $list{"key"}
65 12         17 my ($first, $last);
66 0         0 my %list;
67 12         24 my $next = []; # dummy
68 12         34 while (@_) {
69             # Traverse the arg list backwards so we get most recent keys first.
70 39         55 my ($value, $key) = (pop, pop); # gets a treat?
71 39 100       237 $first = $list{$key} = $next = [ $key, $value, undef, $next ]
72             unless $list{$key};
73 39   66     145 $last ||= $first;
74             # We're going backwards, so the last shall be first (Matthew 20:16)
75             }
76 12         47 $_->[next_link]->[prev_link] = $_ foreach values %list;
77 12         24 $last->[next_link] = undef; # get rid of dummy
78              
79             # [ lookup-list, first-link, last-link, iter-link ]
80 12         66 return bless [ \%list, $first, $last, undef ], $class;
81             }
82             else {
83 2         12 return bless [ {}, undef, undef, undef ], $class;
84             }
85             }
86              
87             sub FETCH {
88 80     80   248 my ($self, $key) = @_;
89 80 100       191 return unless exists $self->[lookup]->{$key};
90              
91 78         129 return $self->[lookup]->{$key}->[value];
92             }
93              
94             sub STORE {
95 22     22   4728 my ($self, $key, $value) = @_;
96              
97 22         31 my $list_entry;
98 22 100       55 if (exists $self->[lookup]->{$key}) {
99             # Hard case: we're updating an existing element
100              
101 6         12 $list_entry = $self->[lookup]->{$key};
102 6 100       16 if ($list_entry == $self->[last_link]) {
103             # When an element is stored, move it to the end of our list of keys.
104             # In this case, though, we're updating the last element, so there's
105             # no need for costly rearrangement.
106 1         24 return ($list_entry->[value] = $value);
107             }
108              
109             # Classic first year CS stuff to update the doubly linked list
110             # (do they even teach CS anywhere anymore?)
111              
112             # First, remove our list-entry from its current position in the list ...
113 5 100       15 $list_entry->[prev_link]->[next_link] = $list_entry->[next_link]
114             if $list_entry->[prev_link];
115 5 50       13 $list_entry->[next_link]->[prev_link] = $list_entry->[prev_link]
116             if $list_entry->[next_link];
117              
118             # Update the hash iterator as appropriate ...
119 5 50 66     16 $self->[iter_link] = $list_entry->[next_link]
120             if $self->[iter_link] && $self->[iter_link] == $list_entry;
121              
122             # Update first-link if our list-entry was at the beginning of the list
123 5 100 66     12 $self->[first_link] = $list_entry->[next_link]
124             if $self->[first_link] && $self->[first_link] == $list_entry;
125             }
126             else {
127             # Easy case: we're simply adding a new entry at the end
128 16         40 $list_entry = [ $key ];
129 16         36 $self->[lookup]->{$key} = $list_entry;
130             }
131              
132             # More CS stuff ... Aho & Ullman p78, second code snippet
133 21 100       49 $self->[last_link]->[next_link] = $list_entry if $self->[last_link];
134 21         45 $list_entry->[prev_link] = $self->[last_link];
135 21         43 $list_entry->[next_link] = undef;
136 21         35 $self->[last_link] = $list_entry;
137 21 100       37 $self->[first_link] = $list_entry unless $self->[first_link];
138              
139 21         51 return ($list_entry->[value] = $value);
140             }
141              
142             sub EXISTS {
143 6     6   22 my ($self, $key) = @_;
144 6         16 return exists $self->[lookup]->{$key};
145             }
146              
147             sub DELETE {
148 6     6   16 my ($self, $key) = @_;
149 6 100       14 return unless exists $self->[lookup]->{$key};
150              
151 5         10 my $list_entry = $self->[lookup]->{$key};
152 5 100 66     12 $self->[first_link] = $list_entry->[next_link]
153             if $self->[first_link] && $self->[first_link] == $list_entry;
154 5 100 66     12 $self->[last_link] = $list_entry->[prev_link]
155             if $self->[last_link] && $self->[last_link] == $list_entry;
156              
157 5 50 33     12 $self->[iter_link] = $list_entry->[next_link]
158             if $self->[iter_link] && $self->[iter_link] == $list_entry;
159              
160 5 100       10 $list_entry->[next_link]->[prev_link] = $list_entry->[prev_link]
161             if $list_entry->[next_link];
162 5 100       10 $list_entry->[prev_link]->[next_link] = $list_entry->[next_link]
163             if $list_entry->[prev_link];
164              
165 5         11 return delete $self->[lookup]->{$key};
166             }
167              
168             sub CLEAR {
169 0     0   0 my ($self) = @_;
170              
171 0         0 $self->[lookup] = {};
172 0         0 $self->[first_link] = undef;
173 0         0 $self->[last_link] = undef;
174 0         0 $self->[iter_link] = undef;
175              
176 0         0 return;
177             }
178              
179             sub FIRSTKEY {
180 52     52   3239 my ($self) = @_;
181 52 100       102 return unless $self->[first_link];
182              
183 49         94 $self->[iter_link] = $self->[first_link]->[next_link];
184 49         86 my $list_entry = $self->[first_link];
185 49 50       135 return wantarray? ($list_entry->[key], $list_entry->[value])
186             : $list_entry->[key];
187             }
188              
189             sub NEXTKEY {
190 144     144   3979 my ($self) = @_;
191              
192 144         255 my $iter = $self->[iter_link];
193 144         255 $self->[iter_link] = $iter->[next_link];
194              
195 144 50       317 return wantarray? ($iter->[key], $iter->[value])
196             : $iter->[key];
197             }
198              
199             1;
200              
201             __END__