File Coverage

blib/lib/Array/Tour/Spiral.pm
Criterion Covered Total %
statement 145 148 97.9
branch 74 76 97.3
condition 14 18 77.7
subroutine 11 12 91.6
pod 3 3 100.0
total 247 257 96.1


line stmt bran cond sub pod time code
1             package Array::Tour::Spiral;
2              
3 6     6   156434 use 5.008;
  6         27  
  6         238  
4 6     6   32 use strict;
  6         11  
  6         327  
5 6     6   34 use warnings;
  6         21  
  6         186  
6 6     6   6899 use integer;
  6         64  
  6         35  
7 6     6   194 use base q(Array::Tour);
  6         16  
  6         4278  
8 6     6   31 use Array::Tour qw(:directions :status);
  6         11  
  6         12535  
9              
10             our $VERSION = '0.06';
11              
12             =head1 NAME
13              
14             Array::Tour::Spiral - Return coordinates to take a spiral path.
15              
16             =head1 SYNOPSIS
17              
18             use Array::Tour::Spiral qw(:directions);
19              
20             my $spiral = Array::Tour::Spiral->new(
21             dimensions => [5, 5],
22             counterclock => $counterclock,
23             corner_right => $corner_right,
24             corner_bottom => $corner_bottom
25             inward => $inward);
26              
27             Creates the object with its attributes. The attributes are:
28              
29             =over 4
30              
31             =item dimensions
32              
33             Set the size of the grid:
34              
35             my $spath1 = Array::Tour::Spiral->new(dimensions => [16, 16]);
36              
37             If the grid is going to be square, a single integer is sufficient:
38              
39             my $spath1 = Array::Tour::Spiral->new(dimensions => 16);
40              
41             =item counterclock corner_bottom corner_right inward
42              
43             I All are boolean values that affect the starting
44             point and the direction of the spiral path. By default, the spiral is
45             generated outwards from the center, using the upper left corner (if
46             there is a choice), in a clockwise direction. See the Examples section
47             to see what effects the different combinations produce.
48              
49             =back
50              
51             =head1 PREREQUISITES
52              
53             Perl 5.8 or later. This is the version of perl under which this module
54             was developed.
55              
56             =head1 DESCRIPTION
57              
58             A simple iterator that will return the coordinates of the next cell if
59             one were to tour a matrice's cells in a spiral path.
60              
61             =head2 Spiral Object Methods
62              
63             =head3 direction
64              
65             $dir = $tour->direction()
66              
67             Return the direction we just walked.
68              
69             Overrides Array::Tour's direction() method.
70              
71             =cut
72              
73             sub direction()
74             {
75 0     0 1 0 my $self = shift;
76 0 0       0 return ($self->{status} == STOP)? undef: ${$self->{direction}}[0];
  0         0  
77             }
78              
79             =head3 next()
80              
81             Returns an array reference to the next coordinates to use. Returns
82             undef if there is no next cell to visit.
83              
84             my $ctr = 1;
85             my $tour = Array::Tour::Spiral->new(dimensions => 64);
86              
87             while (my $cref = $tour->next())
88             {
89             my($x_coord, $y_coord, $z_coord) = @{$cref};
90             $grid[$y_coord, $x_coord] = isprime($ctr++);
91             }
92              
93             The above example generates Ulam's Spiral
94             L in the array @grid.
95              
96             Overrides Array::Tour's next() method.
97              
98             =cut
99              
100             sub next()
101             {
102 7776     7776 1 70515 my $self = shift;
103              
104 7776 100       26724 return undef unless ($self->has_next());
105              
106             #
107             # Set up the conditions for the pacing.
108             # The first pacing value is incremented by one for inward
109             # spirals because by the time it is used for the second time
110             # it won't be shortened by a perpendicular branch of the walk.
111             #
112 7584 100       15467 if ($self->{tourstatus} == START)
113             {
114 192         305 $self->{tourstatus} = TOURING;
115 192         214 $self->{pacer} = ${$self->{pacing}}[0];
  192         481  
116 192 100       481 ${$self->{pacing}}[0] += 1 if ($self->{inward});
  96         198  
117             }
118             else
119             {
120             #
121             # Pace off in the current direction.
122             #
123 7392         9332 my $direction = ${$self->{direction}}[0];
  7392         13770  
124 7392 100       18809 ${$self->{position}}[(($direction & (North | South)) == 0)? 0: 1] +=
  7392 100       27309  
125             (($direction & (North | West)) == 0)? 1: -1;
126              
127             #
128             # Will the next pace be in a different direction?
129             #
130 7392 100       28108 if (--$self->{pacer} == 0)
131             {
132 1888         1972 $self->{pacer} = ${$self->{pacing}}[1];
  1888         4047  
133 1888         2269 ${$self->{pacing}}[0] += $self->{pacechange};
  1888         3245  
134            
135             #
136             # Rotate to the next pacing length and the next direction.
137             #
138 1888         2056 push @{$self->{pacing}}, shift @{$self->{pacing}};
  1888         3071  
  1888         13222  
139 1888         2193 push @{$self->{direction}}, shift @{$self->{direction}};
  1888         2724  
  1888         4025  
140             }
141              
142             }
143              
144 7584 100       19049 $self->{tourstatus} = STOP if (++$self->{odometer} == $self->{tourlength});
145 7584         19699 return $self->adjusted_position();
146             }
147              
148             =head3 anti_spiral()
149              
150             $larips = $spiral->anti_spiral();
151              
152             Return a new object that follows the same path as the original object,
153             reversing the inward/outward direction.
154              
155             =cut
156              
157             sub anti_spiral()
158             {
159 96     96 1 57854 my $self = shift;
160 96         175 my %anti_self;
161 96         133 my @dimensions = @{ $self->{dimensions} };
  96         329  
162              
163 96         1486 $anti_self{dimensions} = $self->{dimensions};
164 96         305 $anti_self{counterclock} = $self->{counterclock} ^ 1;
165 96         733 $anti_self{inward} = $self->{inward} ^ 1;
166              
167 96         131 my $width = $dimensions[0];
168 96         135 my $height = $dimensions[1];
169              
170 96 100       304 if ($width == $height)
    100          
171             {
172 32         48 my $is_even = $height & 1;
173 32         91 $anti_self{corner_right} = $is_even ^ $self->{counterclock} ^ $self->{corner_bottom} ^ 1;
174 32         85 $anti_self{corner_bottom} = $is_even ^ $self->{counterclock} ^ $self->{corner_right};
175             }
176             elsif ($width > $height)
177             {
178 32         50 my $is_even = $height & 1;
179 32         64 $anti_self{corner_right} = $is_even ^ $self->{corner_right};
180 32         69 $anti_self{corner_bottom} = $is_even ^ $self->{counterclock} ^ $self->{corner_bottom};
181             }
182             else #$width < $height
183             {
184 32         54 my $is_even = $width & 1;
185 32         92 $anti_self{corner_right} = $is_even ^ $self->{counterclock} ^ $self->{corner_right};
186 32         73 $anti_self{corner_bottom} = $is_even ^ $self->{corner_bottom};
187             }
188              
189 96         501 return Array::Tour::Spiral->new(%anti_self);
190             }
191              
192             =head3 _set()
193              
194             $self->_set(%parameters);
195              
196             Override Array::Tour's _set() method for one that can handle
197             our parameters.
198             =cut
199              
200             sub _set()
201             {
202 192     192   263 my $self = shift;
203 192         627 my(%params) = @_;
204              
205 192         613 warn "Unknown paramter $_" foreach (grep{$_ !~ /inward|counterclock|corner_right|corner_bottom/} (keys %params));
  768         3666  
206              
207             #
208             # Set counterclock, corner_right, corner_bottom, and inward
209             # to 0/1 values.
210             #
211 192 100 66     1610 $self->{counterclock} = (defined $params{counterclock} and $params{counterclock} != 0)? 1: 0;
212 192 100 66     959 $self->{corner_right} = (defined $params{corner_right} and $params{corner_right} != 0)? 1: 0;
213 192 100 66     1077 $self->{corner_bottom} = (defined $params{corner_bottom} and $params{corner_bottom} != 0)? 1: 0;
214 192 100 66     1133 $self->{inward} = (defined $params{inward} and $params{inward} != 0)? 1: 0;
215              
216 192 100       690 return $self->_set_inward() if ($self->{inward} == 1);
217 96         257 return $self->_set_outward();
218             }
219              
220             =head3 _set_inward()
221              
222             $self->_set_inward();
223              
224             Set the attributes knowing that the spiral path goes inward.
225             =cut
226              
227             sub _set_inward()
228             {
229 96     96   136 my $self = shift;
230 96         127 my @dimensions = @{ $self->{dimensions} };
  96         265  
231 96         164 my $width = $dimensions[0];
232 96         148 my $height = $dimensions[1];
233 96         150 my $counterclock = $self->{counterclock};
234 96         140 my $corner_bottom = $self->{corner_bottom};
235 96         149 my $corner_right = $self->{corner_right};
236 96         142 my $pace_x = $width - 1;
237 96         131 my $pace_y = $height - 1;
238 96         223 my @direction = (East, South, West, North);
239 96         221 my($start_x, $start_y) = (0, 0);
240 96         116 my $rotate;
241              
242 96         169 $self->{pacechange} = -1;
243 96 100       219 $start_x = $width - 1 if ($corner_right);
244 96 100       238 $start_y = $height - 1 if ($corner_bottom);
245              
246 96         201 $rotate = ($corner_bottom << 1) | ($corner_bottom ^ $corner_right);
247 96 100       203 $rotate ^= 2 if ($counterclock);
248 96         245 push @direction, splice(@direction, 0, $rotate);
249 96 100       246 @direction = reverse @direction if ($counterclock);
250              
251 96         196 $self->{direction} = \@direction;
252 96 100       411 $self->{pacing} = (($direction[0] & (West | East)) == 0)? [$pace_y, $pace_x]: [$pace_x, $pace_y];
253 96         223 $self->{start} = [$start_x, $start_y];
254 96         260 $self->{position} = [$start_x, $start_y];
255 96         478 $self->{rotate} = $rotate;
256              
257 96         453 return $self;
258             }
259              
260             =head3 _set_outward()
261              
262             $self->_set_outward();
263              
264             Set the attributes knowing that the spiral path goes outward.
265             =cut
266              
267             sub _set_outward()
268             {
269 96     96   126 my $self = shift;
270 96         122 my @dimensions = @{ $self->{dimensions} };
  96         309  
271 96         195 my $width = $dimensions[0];
272 96         122 my $height = $dimensions[1];
273 96         145 my $counterclock = $self->{counterclock};
274 96         155 my $corner_bottom = $self->{corner_bottom};
275 96         134 my $corner_right = $self->{corner_right};
276 96         156 my($pace_x, $pace_y) = (1, 1);
277 96         216 my @direction = (East, South, West, North);
278 96         123 my($start_x, $start_y, $rotate);
279              
280 96         169 $self->{pacechange} = 1;
281              
282             #
283             # Find the starting corner.
284             #
285 96         154 $start_x = ($width-1)/2;
286 96         120 $start_y = ($height-1)/2;
287              
288 96 100       244 if ($width == $height)
    100          
289             {
290             #
291             # Adjust the starting corner if it's an even side.
292             #
293 32 100       88 if (($width & 1) == 0)
294             {
295 16 100       37 $start_x++ if ($corner_right);
296 16 100       39 $start_y++ if ($corner_bottom);
297             }
298              
299             #
300             # Circling clockwise from top left to bottom left the
301             # corner flags are [00], [01], [11], [10]. In other
302             # words, a two bit Gray code that converts to 0..3,
303             # which we'll use to rotate the direction list.
304             #
305 32         58 $rotate = ($corner_bottom << 1) | ($corner_bottom ^ $corner_right);
306 32 100       86 $rotate ^= 2 if ($counterclock);
307             }
308             elsif ($width > $height) # X-axis is the major axis.
309             {
310 32         41 $pace_x += $width - $height;
311 32         35 $start_x = $start_y;
312              
313 32 100       71 if (($corner_right ^ $corner_bottom) == 1)
314             {
315 16         19 $pace_x--;
316 16 100       38 $start_x++ if (($height & 1) == 0);
317             }
318              
319 32 100       76 $start_x = ($width - 1) - $start_x if ($corner_right);
320              
321 32 100 100     114 $start_y++ if (($height & 1) == 0 and ($corner_right ^ $counterclock) == 1);
322              
323 32         47 $rotate = $corner_right << 1;
324 32 100       61 $rotate ^= 1 if ($counterclock);
325             }
326             else # Y-axis is the major axis.
327             {
328 32         53 $pace_y += $height - $width;
329 32         35 $start_y = $start_x;
330              
331 32 100       89 if (($corner_bottom ^ $corner_right) == 1)
332             {
333 16         24 $pace_y--;
334 16 100       48 $start_y++ if (($width & 1) == 0);
335             }
336              
337 32 100       74 $start_y = ($height - 1) - $start_y if ($corner_bottom);
338              
339 32 100 100     135 $start_x++ if (($width & 1) == 0 and ($corner_bottom ^ $counterclock) == 0);
340              
341 32         50 $rotate = ($corner_bottom << 1) | 1;
342 32 100       77 $rotate ^= 3 if ($counterclock);
343             }
344              
345 96         458 push @direction, splice(@direction, 0, $rotate);
346 96 100       231 @direction = reverse @direction if ($counterclock);
347              
348 96         195 $self->{direction} = \@direction;
349 96 100       349 $self->{pacing} = (($direction[0] & (West | East)) == 0)? [$pace_y, $pace_x]: [$pace_x, $pace_y];
350 96         234 $self->{start} = [$start_x, $start_y];
351 96         267 $self->{position} = [$start_x, $start_y];
352 96         402 $self->{rotate} = $rotate;
353              
354 96         517 return $self;
355             }
356             1;
357             __END__