File Coverage

blib/lib/Grid/Coord.pm
Criterion Covered Total %
statement 127 135 94.0
branch 40 48 83.3
condition 14 18 77.7
subroutine 38 38 100.0
pod 1 25 4.0
total 220 264 83.3


line stmt bran cond sub pod time code
1             package Grid::Coord;
2 1     1   679 use strict; use warnings;
  1     1   3  
  1         29  
  1         5  
  1         2  
  1         23  
3            
4 1     1   1117 use Data::Dumper;
  1         26862  
  1         88  
5 1     1   9 use Carp qw/confess/;
  1         2  
  1         602  
6            
7             our $VERSION = '0.05';
8             # $Id$
9            
10             ########################################### main pod documentation begin ##
11            
12             =head1 NAME
13            
14             Grid::Coord - abstract representation and manipulation of points and rectangles
15            
16             =head1 SYNOPSIS
17            
18             use Grid::Coord
19             my $point1 = Grid::Coord->new(5,4); # point(y=>5, x=>4)
20             my $rect1 = Grid::Coord->new(2,3 => 6,5); # rectangle
21             print "TRUE" if $rect1->contains($point1);
22            
23             my $rect2 = Grid::Coord->new(3,4 => 5,5); # another rectangle
24             my $rect3 = $rect1->overlap($rect2) # (3,4 => 5,5)
25             print $rect3->stringify; # "(3,4 => 5,5)"
26             print $rect3; # "(3,4 => 5,5)"
27             print "TRUE" if $rect3->equals(Grid::Coord->new(3,4 => 5,5));
28             print "TRUE" if $rect3 == Grid::Coord->new(3,4 => 5,5);
29            
30             =head1 DESCRIPTION
31            
32             Manage points or rectangles on a grid. This is generic, and could
33             be used for spreadsheets, ascii art, or other nefarious purposes.
34            
35             =head1 USAGE
36            
37             =head2 Constructor
38            
39             Grid->Coord->new($y, $x);
40             Grid->Coord->new($min_y, $min_x, $max_y, $max_x);
41            
42             =head2 Accessing coordinates
43            
44             The C, C, C, C functions:
45            
46             print $coord->max_x; # get value
47             $coord->min_x(4); # set value to 4
48            
49             =head2 Relationships with other Coords
50            
51             $c3 = $c1->overlap($c2);
52             print "TRUE" if $rect1->contains($rect2);
53             print "TRUE" if $rect1->equals($rect2);
54            
55             =head2 Overloaded operators
56            
57             Four operators are overloaded:
58            
59             =over 4
60            
61             =item * the stringification operator
62            
63             So that C does something reasonable
64            
65             =item * the equality operator
66            
67             so that C
68             does the right thing.
69            
70             =item * the add operator
71            
72             So that C<$c1 + $c2> is a synonym for C<$c1->offset($c2)>
73            
74             =item * the subtract operator
75            
76             So that C<$c1 - $c2> is a synonym for C<$c1->delta($c2)>
77            
78             =back
79            
80             =head2 Iterating
81            
82             The iterator returns a Grid::Coord object for each cell in the current
83             Grid::Coord range.
84            
85             my $it = $grid->cell_iterator; # or ->cell_iterator_rowwise
86             # my $it = $grid->cell_iterator_colwise; # top to bottom
87            
88             while (my $cell = $it3->()) {
89             # do something to $cell
90             }
91            
92             You can also iterate columns/rows with
93             $grid->cells_iterator
94             $grid->rows_iterator
95            
96             =head1 BUGS
97            
98             None reported yet.
99            
100             =head1 SUPPORT
101            
102             From the author.
103            
104             =head1 AUTHOR
105            
106             osfameron@cpan.org
107             http://osfameron.perlmonk.org/
108            
109             =head1 COPYRIGHT
110            
111             This program is free software; you can redistribute
112             it and/or modify it under the same terms as Perl itself.
113            
114             The full text of the license can be found in the
115             LICENSE file included with this module.
116            
117            
118             =head1 SEE ALSO
119            
120             perl(1).
121            
122             =cut
123            
124             use overload
125 1         14 q("") => \&stringify,
126             q(==) => \&equals,
127             q(!=) => \¬_equals,
128             q(+) => \&offset,
129 1     1   7 q(-) => \δ
  1         2  
130            
131             sub new
132             {
133 267     267 0 761 my $class = shift;
134            
135 267 100       678 if (@_ == 2) {
    50          
136 70         120 push @_, @_;
137             } elsif (@_ != 4) {
138 0         0 die "Grid::Coord objects must be (y,x) or (miny,minx=>maxy,maxx)\n";
139             }
140            
141 267   66     1330 my $self = bless [@_], (ref ($class) || $class);
142 267         1031 return ($self);
143             }
144            
145             sub min_y {
146 210     210 0 222 my $self=shift;
147 210 50       871 if (! @_) { return $self->[0] }
  210         496  
148 0         0 else { $self->[0] = shift }
149             }
150             sub min_x {
151 206     206 0 229 my $self=shift;
152 206 50       307 if (! @_) { return $self->[1] }
  206         446  
153 0         0 else { $self->[1] = shift }
154             }
155             sub max_y {
156 206     206 0 216 my $self=shift;
157 206 50       306 if (! @_) { return $self->[2] }
  206         462  
158 0         0 else { $self->[2] = shift }
159             }
160             sub max_x {
161 206     206 0 207 my $self=shift;
162 206 50       295 if (! @_) { return $self->[3] }
  206         474  
163 0         0 else { $self->[3] = shift }
164             }
165            
166             sub is_point {
167 460     460 0 504 my $self = shift;
168 460         664 for (0..1) {
169 716         1128 my ($min, $max) = ($self->[$_], $self->[$_+2]);
170 716 100 66     2447 return unless defined $min && defined $max;
171 405 100       962 return unless $min == $max;
172             }
173 49         132 return 1;
174             }
175            
176             sub overlap {
177 103     103 0 358 my ($self, $other)=@_;
178 103 50       323 if (! $other->isa(__PACKAGE__)) {
179 0         0 die "Can't overlap with something that isn't a Grid::Coord object!\n";
180             }
181 103         201 my @coords = (
182             max($self->min_y, $other->min_y),
183             max($self->min_x, $other->min_x),
184             min($self->max_y, $other->max_y),
185             min($self->max_x, $other->max_x)
186             );
187 103 100 100     453 return if ($coords[0] > $coords[2] or
188             $coords[1] > $coords[3]);
189 88         183 return $self->new(@coords);
190             }
191            
192             sub contains {
193 2     2 0 6 my ($self, $other)=@_;
194 2 50       10 if (! $other->isa(__PACKAGE__)) {
195 0         0 die "Can't 'contains' with something that isn't a Grid::Coord object!\n";
196             }
197 2         5 return ($self->overlap($other) == $other);
198             }
199            
200             sub stringify {
201 460     460 0 1603 my $self=shift;
202 460 100       776 my @rep = map { defined $_ ? $_ : 'null' } @$self;
  1840         3657  
203 460 100       967 if ($self->is_point) {
204 49         218 return "($rep[0],$rep[1])"
205             } else {
206 411         2020 return "($rep[0],$rep[1], $rep[2],$rep[3])";
207             }
208             }
209             sub equals {
210 37     37 0 56 my ($self, $other) = @_;
211 37         65 for (0..3) {
212             return unless
213 123 100 100     545 (defined $self->[$_]) ?
    100          
214             defined $other->[$_] && $self->[$_] == $other->[$_]
215             : ! defined $other->[$_];
216             }
217 26         102 return $self;
218             }
219             sub not_equals {
220             # new versions of Test::Builder seem to make cmp_ok fail on this
221 1     1 0 3 my ($self, $other) = @_;
222 1         4 return ! $self->equals($other);
223             }
224            
225             sub offset { # 'add' 2 ranges together, offsetting them
226 120     120 0 149 my $self=shift;
227 120 100       223 if (ref $_[0] eq "Grid::Coord") {
228 61         79 my $other = shift;
229 61         57 my @coords;
230 61         95 for (0..3) {
231 128     128   4288 push @coords, only($self->[$_],$other->[$_],sub { return $_[0]+$_[1] })
232 244         1250 }
233 61         153 return $self->new(@coords);
234             } else {
235 59         118 return $self->offset($self->new(@_));
236             }
237             }
238             sub delta { # 'subtract' 2 ranges together, calculating the offest
239 3     3 0 10 my $self=shift;
240 3 50       8 if (ref $_[0] eq "Grid::Coord") {
241 3         4 my $other = shift;
242 3         3 my @coords;
243 3         6 for (0..3) {
244 12     12   53 push @coords, only($self->[$_],$other->[$_],sub { return $_[1]-$_[0] })
245 12         41 }
246 3         7 return $self->new(@coords);
247             } else {
248 0         0 return $self->offset($self->new(@_));
249             }
250             }
251            
252 3     3 0 1450 sub head { my $self=shift; return $self->new($self->[0], $self->[1]) }
  3         12  
253 2     2 0 4 sub tail { my $self=shift; return $self->new($self->[2], $self->[3]) }
  2         7  
254            
255 15     15 0 19 sub row {my $self=shift; return $self->new($self->[0],undef,$self->[0],undef)}
  15         41  
256 16     16 0 18 sub col {my $self=shift; return $self->new(undef, $self->[1],undef,$self->[1])}
  16         44  
257            
258 206   66 206 0 345 sub min { return only(@_) || (($_[0] < $_[1]) ? $_[0] : $_[1]) }
259 206   66 206 0 333 sub max { return only(@_) || (($_[0] > $_[1]) ? $_[0] : $_[1]) }
260            
261            
262            
263             =begin developer
264            
265             =head3 only
266            
267             A convenience function. Has 2 forms.
268            
269             only($a, $b);
270            
271             The 2 arg form returns the other argument if one argument is undef.
272             (As a consequence, if both are null, it returns null). If neither are undef,
273             it also returns null. This is useful for the min and max functions, where
274             we want to be able to calculate intersections, but also of row and column
275             ranges where one side may be undef).
276            
277             only($a, $b, sub { ... });
278            
279             Again, returns the other argument if one argument is undef. However, if neither
280             is undef it passes both arguments to the coderef. This is useful in calculating
281             offsets, where we want this kind of behaviour:
282            
283             0 + 0 = 0
284             undef + 0 = 0
285             undef + undef = undef
286            
287             =cut
288            
289             sub only {
290 668 100   668 1 1979 if (! defined $_[0]) { return $_[1]}
  314         1052  
291 354 100       581 if (! defined $_[1]) { return $_[0]}
  82         258  
292 272 100       455 if (my $coderef=$_[2]) {
293 140         276 return $coderef->(@_)
294             } else {
295             return
296 132         591 }
297             }
298            
299             {
300 1     1   1474 no warnings 'once';
  1         1  
  1         484  
301             *cell_iterator=\&cell_iterator_rowwise;
302             }
303             sub cell_iterator_rowwise {
304 1     1 0 3 my $self=shift;
305 6     6   14 return $self->_cell_iterator(
306             $self->rows_iterator,
307 1         6 sub{$self->cols_iterator});
308             }
309             sub cell_iterator_colwise {
310 1     1 0 2 my $self=shift;
311 5     5   19 return $self->_cell_iterator(
312             $self->cols_iterator,
313 1         3 sub{$self->rows_iterator});
314             }
315            
316             sub _cell_iterator {
317             # We pass in the major-line iterator as an iterator.
318             # However, as the minor-line iterator will be created
319             # various times, we pass in a factory function instead!
320            
321 2     2   5 my ($self, $maj_it, $min_fac) = @_;
322 2         4 my $min_it = $min_fac->();
323            
324 2         4 my $maj=$maj_it->();
325             return sub {
326             {
327 42 100   42   190 return unless $maj;
  51         96  
328 49 100       93 if (my $min=$min_it->()) {
329 40         91 return $maj->overlap($min)
330             } else {
331 9         17 $maj = $maj_it->();
332 9         23 $min_it = $min_fac->();
333 9         36 redo;
334             }
335             }
336             }
337 2         21 }
338             sub rows_iterator {
339 7     7 0 10 my $self=shift;
340 7         17 my $row=$self->row;
341 7         19 return $self->line_iterator($row, 1, undef);
342             }
343             sub cols_iterator {
344 8     8 0 12 my $self=shift;
345 8         16 my $col=$self->col;
346 8         27 return $self->line_iterator($col, undef, 1);
347             }
348            
349             sub line_iterator {
350 15     15 0 28 my ($self, $orig_line, $y, $x)=@_;
351 15         15 my $line=$orig_line;
352             return sub {
353            
354             # TODO: warning on next line in eq
355             #if ($_[0] eq 'clone') { die;return line_iterator($self,$orig_line, $y,$x) }
356            
357 73     73   3308 my $old_line = $line;
358 73 100       123 if ($line) {
359 58         133 $line = $line->offset($y,$x);
360 58 100       158 if (! $line->overlap($self)) {
361 13         22 $line=undef;
362             }
363             }
364 73         225 return $old_line;
365             }
366 15         88 }
367            
368             1; #this line is important and will help the module return a true value
369             __END__