File Coverage

blib/lib/TipJar/sparse/array/perl/hashbased.pm
Criterion Covered Total %
statement 124 134 92.5
branch 30 44 68.1
condition n/a
subroutine 20 22 90.9
pod 0 4 0.0
total 174 204 85.2


line stmt bran cond sub pod time code
1             package TipJar::sparse::array::perl::hashbased;
2              
3 1     1   56051 use strict;
  1         2  
  1         452  
4              
5             require Exporter;
6              
7             our @ISA = qw(Exporter);
8              
9             our @EXPORT = qw(
10             sparse
11             );
12              
13             our $VERSION = '0.01';
14              
15              
16             sub sparse(\@){
17 1     1 0 20 tie @{$_[0]}, __PACKAGE__
  1         12  
18             };
19              
20             sub data(){ 0 } # the hash that stores the values
21             sub offset() { 1 } # adjustment to index, for fast shift/unshift
22             sub sortedkeys() { 2 } # should always be the same as numeric-sorted keys
23 66     66 0 375 sub top {3} # one more than the normalized index of the highest element
24              
25 2     2   535 sub CLEAR { my ($this) = shift;
26 2         34 @$this = ({},0,[],0)
27             }
28              
29             sub TIEARRAY{ # classname, LIST
30 1     1   4 my $this = bless [];
31 1         6 $this->CLEAR;
32 1         4 $this
33             }
34 357     357 0 958 sub normalize { my ($this, $key) = @_;
35 357         376 $key = int $key;
36 357 100       740 $key < 0 and $key += $this->[top];
37 357         3294 $key + $this->[offset];
38             }
39 165     165   135043 sub EXISTS { my ($this, $key) = @_;
40 165         765 exists $this->[data]->{$this->normalize($key)}
41             }
42 173     173   237 sub FETCH { my ($this, $key) = @_;
43 173         295 $this->[data]->{$this->normalize($key)}
44             }
45 1     1   7 use Carp ();
  1         2  
  1         1675  
46 2     2   870 sub DELETE { my ($this, $key) = @_;
47 2         6 my $N = $this->normalize($key);
48             ### SIZE REDUCTION ON DELETION OF LAST ELEMENT:
49 2 100       12 if ( (1+$N) == $this->[top] ){
50 1 50       7 $N == $this->[sortedkeys]->[-1] and pop @{$this->[sortedkeys]};
  0         0  
51 1         3 my $newtop = $this->[sortedkeys]->[-1];
52 1 50       4 if (defined $newtop){
53 1         3 $this->[top] = 1+$newtop
54             }else{
55 0         0 $this->[top] = $this->[offset]
56             }
57             } else {
58 0         0 exists $this->[data]->{$N}
59 1 50       7 and splice @{$this->[sortedkeys]}, $this->LocateKey($N), 1;
60             }
61 2         10 delete $this->[data]->{$N};
62             }
63 11     11 0 21 sub LocateKey{ my ($this, $N) = @_; # return an OFFSET for splice in DELETE and STORE
64 11         14 my ($lower, $upper) = (0, $#{$this->[sortedkeys]});
  11         28  
65              
66 11         41 while ($lower < $upper){
67 15         32 my $guess = int (( 1+ $lower + $upper) / 2);
68            
69 15         28 my $val = $this->[sortedkeys]->[$guess];
70 15 100       36 $val == $N and return $guess;
71 14 100       32 if ($val > $N){
72 6         15 $upper = $guess - 1;
73             }else{
74 8         23 $lower = $guess;
75             }
76              
77             };
78 10         23 $lower
79              
80             }
81 6     6   31 sub STORE { my ($this, $key, $value) = @_;
82 6         17 my $N = $this->normalize($key);
83 6 50       23 $N < $this->[offset]
84             and Carp::croak "Modification of non-creatable array value attempted, subscript $key";
85 6 50       22 unless (exists $this->[data]->{$N}){
86 6         18 my $location = 1+$this->LocateKey($N);
87 6         11 splice @{$this->[sortedkeys]}, $location, 0,$N;
  6         18  
88 6 50       16 $this->[top] > $N or $this->[top] = ($N+1);
89             };
90 6         41 $this->[data]->{$N} = $value;
91             }
92 26     26   738 sub FETCHSIZE { my ($this) = @_;
93 26         58 $this->[top] - $this->[offset]
94             }
95 1     1   396 sub STORESIZE { my ($this, $count) = @_;
96 1         3 $count = int $count;
97 1 50       4 $count <= 0 and return $this->CLEAR;
98 1         4 my $before = $this->FETCHSIZE;
99 1 50       5 $before == $count and return; # no-op
100 1 50       4 if ($before < $count){ # extend the apparent length
101 0         0 $this->[top] = $this->[offset]+$count;
102             return
103 0         0 };
104             # delete [$count] and all elements north of it
105 1         4 my $N = $this->normalize($count - 1);
106 1         5 while ($this->[sortedkeys]->[-1] > $N ){
107 0         0 my $nn = pop @{$this->[sortedkeys]};
  0         0  
108 0         0 delete $this->[data]->{$nn};
109             }
110 1         591 $this->[top] = $this->[offset]+$count;
111             }
112 1     1   3 sub PUSH { my ($this, @LIST) = @_;
113 1         7 while (@LIST){
114 1         6 $this->[data]->{$this->[top]} = shift @LIST;
115 1         3 push @{$this->[sortedkeys]}, $this->[top]++
  1         4  
116             };
117             }
118 1     1   413 sub POP { my ($this) = @_;
119 1 50       6 if (exists $this->[data]->{--$this->[top]}){
120 1         2 pop @{$this->[sortedkeys]};
  1         23  
121 1         4 return delete $this->[data]->{$this->[top]}
122             }
123             }
124 1     1   466 sub SHIFT { my ($this) = @_;
125 1 50       4 $this->[top] == $this->[offset] and return undef;
126 1 50       6 $this->[sortedkeys]->[0] == $this->[offset] and shift @{$this->[sortedkeys]};
  1         3  
127 1         7 delete $this->[data]->{$this->[offset]++};
128             }
129 1     1   4 sub UNSHIFT { my ($this, @LIST) = @_;
130 1         2 my $offset = $this->[offset];
131 1         6 while (@LIST){
132 1         4 $this->[data]->{--$offset} = pop @LIST;
133 1         3 unshift @{$this->[sortedkeys]}, $offset
  1         5  
134             };
135 1         5 $this->[offset] = $offset;
136             }
137 5     5   1740 sub SPLICE { my ($this, $offset, $length, @LIST) = @_;
138             # follow the native array semantics of returning existing undef
139             # when returning nonexistent parts; Perl does not fully
140             # support an explicit "unexisting" value at this revision and
141             # very probably never will: nonexistent values spliced in
142             # become existing undefined values.
143              
144 5         15 my $N = $this->normalize($offset);
145 5 100       15 if ($N > $this->[top]){
146 1         216 Carp::carp "splice() offset past end of array";
147 1         214 $N = $this->[top];
148 1         2 $length = 0;
149             };
150 5 100       25 my $Stop = ($length < 0 ? $this->normalize($length) : $this->normalize( $N + $length));
151 5 50       15 $Stop > $this->[top] and $Stop = $this->[top];
152 5 100       14 if( $Stop <= $this->[offset]){
153 2         5 $N = $this->[offset];
154 2         6 $Stop = $N
155             };
156 5         8 my $indexshift = @LIST;
157 5         10 $indexshift -= ($Stop - $N);
158 5         7 $Stop--;
159 5         6 my @retlist;
160 5         13 @retlist = delete @{$this->[data]}{ $N .. $Stop };
  5         19  
161              
162             # in the future, we
163             # can handle all the cases specifically
164             # to minimize number of elements requiring renumbering
165             # but for now we're just going to renumber the top section
166 5 50       17 if ($indexshift){
167 5         21 $this->[top] += $indexshift;
168 5         19 my $first = $this->LocateKey($N);
169 5 100       18 $this->[sortedkeys]->[$first] == $N or $first += 1;
170 5         17 my @oldindices = @{$this->[sortedkeys]}[
  5         13  
171 5         12 $first .. $#{$this->[sortedkeys]}
172             ];
173 5         14 my @newindices = map { $_ + $indexshift } @oldindices ;
  20         40  
174 5         10 my @shifters = delete @{$this->[data]}{ @oldindices };
  5         19  
175 5         14 @{$this->[data]}{ @newindices } = @shifters;
  5         33  
176             };
177              
178 5         20 my @insertkeys = $N .. $N+$#LIST;
179 5         9 @{$this->[data]}{ @insertkeys } = @LIST;
  5         29  
180              
181             # and then clobber [sortedkeys]
182 5         9 $this->[sortedkeys] = [sort { $a <=> $b } keys %{$this->[data]}];
  81         116  
  5         40  
183 5         29 @retlist;
184             }
185 1     1   3 sub EXTEND { my ($this, $count) = @_;
186 1         3 keys %{$this->[data]} = $count;
  1         12  
187             }
188 0     0     sub DESTROY { }
189 0     0     sub UNTIE { }
190              
191             1;
192             __END__