File Coverage

blib/lib/Data/MultiValued/RangeContainer.pm
Criterion Covered Total %
statement 91 99 91.9
branch 32 40 80.0
condition 15 24 62.5
subroutine 15 16 93.7
pod 4 4 100.0
total 157 183 85.7


line stmt bran cond sub pod time code
1             package Data::MultiValued::RangeContainer;
2             {
3             $Data::MultiValued::RangeContainer::VERSION = '0.0.1_4';
4             }
5             {
6             $Data::MultiValued::RangeContainer::DIST = 'Data-MultiValued';
7             }
8 8     8   51 use Moose;
  8         35  
  8         85  
9 8     8   69952 use namespace::autoclean;
  8         21  
  8         93  
10 8     8   1062 use Moose::Util::TypeConstraints;
  8         19  
  8         490  
11 8     8   35221 use MooseX::Types::Moose qw(Num Str Any Undef ArrayRef);
  8         22  
  8         115  
12 8     8   103125 use MooseX::Types::Structured qw(Dict);
  8         4731132  
  8         97  
13 8     8   3168 use Data::MultiValued::Exceptions;
  8         21  
  8         12380  
14              
15             # ABSTRACT: container for ranged values
16              
17              
18             has _storage => (
19             is => 'rw',
20             isa => ArrayRef[
21             Dict[
22             from => Num|Undef,
23             to => Num|Undef,
24             value => Any,
25             ],
26             ],
27             init_arg => undef,
28             default => sub { [ ] },
29             );
30              
31              
32             sub get {
33 131     131 1 399 my ($self,$args) = @_;
34              
35 131         425 my $at = $args->{at};
36              
37 131         616 my ($range) = $self->_get_slot_at($at);
38              
39 131 100       428 if (!$range) {
40 32         502 Data::MultiValued::Exceptions::RangeNotFound->throw({
41             value => $at,
42             });
43             }
44              
45 99         1022 return $range;
46             }
47              
48             # Num|Undef,Num|Undef,Bool,Bool
49             # the bools mean "treat the undef as +inf" (-inf when omitted/false)
50             sub _cmp {
51 567     567   1232 my ($a,$b,$sa,$sb) = @_;
52              
53 567 100 66     2680 $a //= $sa ? 0+'inf' : 0-'inf';
54 567 100 66     1463 $b //= $sb ? 0+'inf' : 0-'inf';
55              
56 567         2530 return $a <=> $b;
57             }
58              
59             # a binary search would be a good idea.
60              
61             sub _get_slot_at {
62 170     170   396 my ($self,$at) = @_;
63              
64 170         313 for my $slot (@{$self->_storage}) {
  170         8212  
65 265 100       1015 next if _cmp($slot->{to},$at,1,0) <= 0;
66 131 100       481 last if _cmp($slot->{from},$at,0,0) > 0;
67 106         322 return $slot;
68             }
69 64         197 return;
70             }
71              
72             # this is quite probably uselessly slow: we don't really need all of
73             # @before and @after, we just need to know if they're not empty; also,
74             # a binary search would be a good idea.
75              
76             sub _partition_slots {
77 23     23   62 my ($self,$from,$to) = @_;
78              
79 23         45 my (@before,@overlap,@after);
80 23         1072 my $st=$self->_storage;
81              
82 23         112 for my $idx (0..$#$st) {
83 43         83 my $slot = $st->[$idx];
84              
85 43         281 my ($sf,$st) = @$slot{'from','to'};
86              
87 43 100       99 if (_cmp($st,$from,1,0) <0) {
    100          
88 15         54 push @before,$idx;
89             }
90             elsif (_cmp($sf,$to,0,1) >=0) {
91 11         39 push @after,$idx;
92             }
93             else {
94 17         67 push @overlap,$idx;
95             }
96             }
97 23         98 return \@before,\@overlap,\@after;
98             }
99              
100              
101             sub get_or_create {
102 39     39 1 114 my ($self,$args) = @_;
103              
104 39         114 my $from = $args->{from};
105 39         102 my $to = $args->{to};
106              
107 39 50       184 Data::MultiValued::Exceptions::BadRange->throw({
108             from => $from,
109             to => $to,
110             }) if _cmp($from,$to,0,1)>0;
111              
112 39         177 my ($range) = $self->_get_slot_at($from);
113              
114 39 50 66     193 if ($range
      33        
115             && _cmp($range->{from},$from,0,0)==0
116             && _cmp($range->{to},$to,1,1)==0) {
117 0         0 return $range;
118             }
119              
120 39         195 $range = $self->_create_slot($from,$to);
121 39         455 return $range;
122             }
123              
124              
125             sub clear {
126 5     5 1 20 my ($self,$args) = @_;
127              
128 5         18 my $from = $args->{from};
129 5         15 my $to = $args->{to};
130              
131 5 50       32 Data::MultiValued::Exceptions::BadRange->throw({
132             from => $from,
133             to => $to,
134             }) if _cmp($from,$to,0,1)>0;
135              
136 5         47 return $self->_clear_slot($from,$to);
137             }
138              
139             sub _create_slot {
140 39     39   91 my ($self,$from,$to) = @_;
141              
142 39         623 $self->_splice_slot($from,$to,{
143             from => $from,
144             to => $to,
145             value => undef,
146             });
147             }
148              
149             sub _clear_slot {
150 5     5   14 my ($self,$from,$to) = @_;
151              
152 5         23 $self->_splice_slot($from,$to);
153             }
154              
155             # Most of the splicing mechanics is here. Given a range and something
156             # to put in it, do "the right thing"
157              
158             sub _splice_slot {
159 44     44   126 my ($self,$from,$to,$new) = @_;
160              
161             # if !$new, it's like C<splice> without a replacement list: we
162             # just delete the range
163              
164 44 100       75 if (!@{$self->_storage}) { # empty, just store
  44         2116  
165 21 50       77 push @{$self->_storage},$new if $new;
  21         1094  
166 21         74 return $new;
167             }
168              
169 23         115 my ($before,$overlap,$after) = $self->_partition_slots($from,$to);
170              
171 23 50 66     150 if (!@$before && !@$overlap) {
172             # nothing before, nothing overlapping: put $new at the beginning
173 0 0       0 unshift @{$self->_storage},$new if $new;
  0         0  
174 0         0 return $new;
175             }
176 23 100 66     135 if (!@$after && !@$overlap) {
177             # nothing after, nothing overlapping: put $new at the end
178 7 50       24 push @{$self->_storage},$new if $new;
  7         324  
179 7         29 return $new;
180             }
181              
182             # ok, we have to insert in the middle of things, and maybe we have
183             # to trim existing ranges
184              
185 16         41 my $first_to_replace;
186 16         38 my $how_many = @$overlap;
187              
188 16 100       78 my @replacement = $new ? ($new) : ();
189              
190 16 100       73 if ($how_many > 0) { # we have to splice
191             # by costruction, the first and the last may have to be split, all
192             # others must be removed
193 14         145 $first_to_replace = $overlap->[0];
194 14         32 my $last_to_replace = $overlap->[-1];
195 14         705 my $first = $self->_storage->[$first_to_replace];
196 14         696 my $last = $self->_storage->[$last_to_replace];
197              
198             # does the first overlapping range need trimming?
199 14 100 66     60 if (_cmp($first->{from},$from,0,0)<0
200             && _cmp($first->{to},$from,1,0)>=0) {
201 7         236 unshift @replacement, {
202             from => $first->{from},
203             to => $from,
204             value => $first->{value},
205             }
206             }
207             # does the last overlapping range need trimming?
208 14 100 66     51 if (_cmp($last->{from},$to,0,1)<=0
209             && _cmp($last->{to},$to,1,1)>0) {
210 5         33 push @replacement, {
211             from => $to,
212             to => $last->{to},
213             value => $last->{value},
214             }
215             }
216             }
217             else {
218             # no overlaps, just insert between @before and @after
219 2         7 $first_to_replace = $before->[-1]+1;
220             }
221              
222 16         32 splice @{$self->_storage},
  16         752  
223             $first_to_replace,$how_many,
224             @replacement;
225              
226 16         109 return $new;
227             }
228              
229              
230             sub all_ranges {
231 0     0 1   my ($self) = @_;
232              
233 0           return map { [ $_->{from}, $_->{to} ] } @{$self->_storage};
  0            
  0            
234             }
235              
236             __PACKAGE__->meta->make_immutable();
237              
238             1;
239              
240             __END__
241             =pod
242              
243             =encoding utf-8
244              
245             =head1 NAME
246              
247             Data::MultiValued::RangeContainer - container for ranged values
248              
249             =head1 VERSION
250              
251             version 0.0.1_4
252              
253             =head1 DESCRIPTION
254              
255             Please don't use this module directly, use L<Data::MultiValued::Ranges>.
256              
257             This module implements the storage for ranged data. It's similar to
258             L<Array::IntSpan>, but simpler (and slower).
259              
260             A range is defined by a pair of numbers, C<from> and C<to>, and it
261             contains C<< Num $x : $min <= $x < $max >>. C<undef> is treated as
262             "inf" (negative infinity if used as C<from> or C<at>, positive
263             infinity if used as C<to>).
264              
265             The internal representation of a range is a hash with three keys,
266             C<from> C<to> C<value>.
267              
268             =head1 METHODS
269              
270             =head2 C<get>
271              
272             my $value = $obj->get({ at => $point });
273              
274             Retrieves the range that includes the given point. Throws a
275             L<Data::MultiValued::Exceptions::RangeNotFound|Data::MultiValued::Exceptions/Data::MultiValued::Exceptions::RangeNotFound>
276             exception if no range includes the point.
277              
278             =head2 C<get_or_create>
279              
280             $obj->get_or_create({ from => $min, to => $max });
281              
282             Retrieves the range that has the given extremes. If no such range
283             exists, creates a new range, splicing any existing overlapping range,
284             and returns it. Throws
285             L<Data::MultiValued::Exceptions::BadRange|Data::MultiValued::Exceptions/Data::MultiValued::Exceptions::BadRange>
286             if C<< $min > $max >>.
287              
288             =head2 C<clear>
289              
290             $obj->clear({ from => $min, to => $max });
291              
292             Removes the range that has the given extremes. If no such range
293             exists, splices any existing overlapping range so that C<<
294             $obj->get({at => $point }) >> for any C<< $min <= $point < $max >>
295             will die.
296              
297             Throws
298             L<Data::MultiValued::Exceptions::BadRange|Data::MultiValued::Exceptions/Data::MultiValued::Exceptions::BadRange>
299             if C<< $min > $max >>.
300              
301             =head2 C<all_ranges>
302              
303             my @ranges = $obj->all_ranges;
304              
305             Returns all the ranges defined in this object, as a list of 2-elements
306             arrayrefs.
307              
308             =head1 AUTHOR
309              
310             Gianni Ceccarelli <dakkar@thenautilus.net>
311              
312             =head1 COPYRIGHT AND LICENSE
313              
314             This software is copyright (c) 2011 by Net-a-Porter.com.
315              
316             This is free software; you can redistribute it and/or modify it under
317             the same terms as the Perl 5 programming language system itself.
318              
319             =cut
320