File Coverage

blib/lib/Array/Iterator.pm
Criterion Covered Total %
statement 78 80 97.5
branch 42 44 95.4
condition 4 6 66.6
subroutine 21 22 95.4
pod 14 14 100.0
total 159 166 95.7


line stmt bran cond sub pod time code
1             package Array::Iterator;
2              
3 10     10   763679 use strict;
  10         19  
  10         389  
4 10     10   51 use warnings;
  10         17  
  10         20438  
5              
6             =head1 NAME
7              
8             Array::Iterator - A simple class for iterating over Perl arrays
9              
10             =head1 VERSION
11              
12             Version 0.135
13              
14             =cut
15              
16             our $VERSION = '0.135';
17              
18             =head1 SYNOPSIS
19              
20             C is a Perl module that provides a simple,
21             uni-directional iterator interface for traversing arrays.
22             It allows users to iterate over arrays, array references, or hash references containing an array, offering methods like next, has_next, peek, and current to facilitate controlled access to elements.
23             The iterator maintains an internal pointer, ensuring elements are accessed sequentially without modifying the underlying array.
24             Tt offers a clean, object-oriented approach to iteration, inspired by Java’s Iterator interface.
25             The module is extendable, allowing subclassing for custom behaviour.
26              
27             use Array::Iterator;
28              
29             # create an iterator with an array
30             my $i = Array::Iterator->new(1 .. 100);
31              
32             # create an iterator with an array reference
33             my $i = Array::Iterator->new(\@array);
34              
35             # create an iterator with a hash reference
36             my $i = Array::Iterator->new({ __array__ => \@array });
37              
38             # a base iterator example
39             while ($i->has_next()) {
40             if ($i->peek() < 50) {
41             # ... do something because
42             # the next element is over 50
43             }
44             my $current = $i->next();
45             # ... do something with current
46             }
47              
48             # shortcut style
49             my @accumulation;
50             push @accumulation => { item => $iterator->next() } while $iterator->has_next();
51              
52             # C++ ish style iterator
53             for (my $i = Array::Iterator->new(@array); $i->has_next(); $i->next()) {
54             my $current = $i->current();
55             # .. do something with current
56             }
57              
58             # common perl iterator idiom
59             my $current;
60             while ($current = $i->get_next()) {
61             # ... do something with $current
62             }
63              
64             It is not recommended to alter the array during iteration, however
65             no attempt is made to enforce this (although I will if I can find an efficient
66             means of doing so). This class only intends to provide a clear and simple
67             means of generic iteration, nothing more (yet).
68              
69             =head2 new (@array | $array_ref | $hash_ref)
70              
71             The constructor can be passed either a plain Perl array, an array reference,
72             or a hash reference (with the array specified as a single key of the hash,
73             __array__).
74             Single-element arrays are not supported by either of the first
75             two calling conventions, since it is not possible to distinguish between an
76             array of a single-element which happens to be an array reference and an
77             array reference of a single element, thus previous versions of the constructor
78             would raise an exception. If you expect to pass arrays to the constructor which
79             may have only a single element, then the array can be passed as the element
80             of a HASH reference, with the key, __array__:
81              
82             my $i = Array::Iterator->new({ __array__ => \@array });
83              
84             =cut
85              
86             sub new {
87 29     29 1 2003075 my ($_class, @array) = @_;
88              
89 29 100       189 (@array) || die 'Insufficient Arguments: you must provide something to iterate over';
90              
91 27   33     339 my $class = ref($_class) || $_class;
92 27         62 my $_array;
93 27 100       187 if (scalar @array == 1) {
94 10 100       58 if (ref $array[0] eq 'ARRAY') {
    50          
95 4         11 $_array = $array[0];
96             } elsif (ref $array[0] eq 'HASH') {
97             die 'Incorrect type: HASH reference must contain the key __array__'
98 6 100       49 unless exists $array[0]->{__array__};
99             die 'Incorrect type: __array__ value must be an ARRAY reference'
100 4 100       27 unless ref $array[0]->{__array__} eq 'ARRAY';
101 3         7 $_array = $array[0]->{__array__};
102             }
103             }
104             else {
105 17         77 $_array = \@array;
106             }
107 24         139 my $iterator = {
108             _current_index => 0,
109             _length => 0,
110             _iteratee => [],
111             _iterated => 0, # -1 when going backwards, +1 when going forwards
112             };
113 24         61 bless($iterator, $class);
114 24         48 $iterator->_init(scalar(@{$_array}), $_array);
  24         155  
115 24         94 return $iterator;
116             }
117              
118             sub _init {
119 26     26   812 my ($self, $length, $iteratee) = @_;
120 26 100 100     251 (defined($length) && defined($iteratee))
121             || die 'Insufficient Arguments: you must provide an length and an iteratee';
122 24         82 $self->{_current_index} = 0;
123 24         70 $self->{_length} = $length;
124             # $self->{_iteratee} = $iteratee;
125              
126             # Store a private copy to prevent modifications
127 24         46 $self->{_iteratee} = [@{$iteratee}];
  24         87  
128             }
129              
130             =head2 _current_index
131              
132             An lvalue-ed subroutine that allows access to the iterator's internal pointer.
133             This can be used in a subclass to access the value.
134              
135             =cut
136              
137             # We need to alter this so it's an lvalue
138             sub _current_index : lvalue {
139 163 100   163   1307 (UNIVERSAL::isa((caller)[0], __PACKAGE__))
140             || die 'Illegal Operation: This method can only be called by a subclass';
141             $_[0]->{_current_index}
142 162         2880 }
143              
144             =head2 _iteratee
145              
146             This returns the item being iterated over, in our case an array.
147              
148             =cut
149              
150             # This we should never need to alter so we don't make it a lvalue
151             sub _iteratee {
152 60 100   60   483 (UNIVERSAL::isa((caller)[0], __PACKAGE__))
153             || die 'Illegal Operation: This method can only be called by a subclass';
154             $_[0]->{_iteratee}
155 59         824 }
156              
157             # we move this from a private method
158             # to a protected one, and check our access
159             # as well
160             sub _getItem {
161 187 100   187   1082 (UNIVERSAL::isa((caller)[0], __PACKAGE__)) || die 'Illegal Operation: This method can only be called by a subclass';
162              
163 186         2070 my ($self, $iteratee, $index) = @_;
164 186         841 return $iteratee->[$index];
165             }
166              
167             =head2 _get_item ($iteratee, $index)
168              
169             This method is used by all other routines to access items. Given the iteratee
170             and an index, it will return the item being stored in the C<$iteratee> at the index
171             of C<$index>.
172              
173             =cut
174              
175 0     0   0 sub _get_item { my $self = shift; $self->_getItem(@_) }
  0         0  
176              
177             # we need to alter this so it's an lvalue
178             sub _iterated : lvalue {
179 59 50   59   193 (UNIVERSAL::isa((caller)[0], __PACKAGE__))
180             || die 'Illegal Operation: This method can only be called by a subclass';
181             $_[0]->{_iterated}
182 59         670 }
183              
184             =head2 iterated
185              
186             Access to the _iterated status, for subclasses
187              
188             =cut
189              
190             sub iterated {
191 2     2 1 7605 my ($self) = @_;
192 2         14 return $self->{_iterated};
193             }
194              
195             =head2 has_next([$n])
196              
197             This method returns a boolean. True (1) if there are still more elements in
198             the iterator, false (0) if there are not.
199              
200             Takes an optional positive integer (E 0) that specifies the position you
201             want to check. This allows you to check if there an element at an arbitrary position.
202             Think of it as an ordinal number you want to check:
203              
204             $i->has_next(2); # 2nd next element
205             $i->has_next(10); # 10th next element
206              
207             Note that C is the same as C.
208              
209             Throws an exception if C<$n> E= 0.
210              
211             =cut
212              
213             sub has_next {
214 83     83 1 627 my ($self, $n) = @_;
215              
216 83 100       225 if(not defined $n) {
    100          
    100          
217 75         137 $n = 1
218             } elsif(not $n) {
219 1         11 die "has_next(0) doesn't make sense, did you mean current()?"
220             } elsif($n < 0) {
221 1         8 die "has_next() with negative argument doesn't make sense, perhaps you should use a BiDirectional iterator"
222             }
223              
224 81         167 my $idx = $self->{_current_index} + ($n - 1);
225              
226 81 100       363 return ($idx < $self->{_length}) ? 1 : 0;
227             }
228              
229             =head2 hasNext
230              
231             Alternative name for has_next
232              
233             =cut
234              
235 61     61 1 15380 sub hasNext { my $self = shift; $self->has_next(@_) }
  61         164  
236              
237             =head2 next
238              
239             This method returns the next item in the iterator, be sure to only call this
240             once per iteration as it will advance the index pointer to the next item. If
241             this method is called after all elements have been exhausted, an exception
242             will be thrown.
243              
244             =cut
245              
246             sub next {
247 60     60 1 8958 my $self = shift;
248              
249 60 100       184 ($self->{_current_index} < $self->{_length}) || die 'Out Of Bounds: no more elements';
250              
251 59         107 $self->{_iterated} = 1;
252 59         199 return $self->_getItem($self->{_iteratee}, $self->{_current_index}++);
253             }
254              
255             =head2 get_next
256              
257             This method returns the next item in the iterator, be sure to only call this
258             once per iteration as it will advance the index pointer to the next item. If
259             this method is called after all elements have been exhausted, it will return
260             undef.
261              
262             This method was added to allow for a fairly common Perl iterator idiom of:
263              
264             my $current;
265             while ($current = $i->get_next()) {
266             ...
267             }
268              
269             In this,
270             the loop terminates once C<$current> is assigned to a false value.
271             The only problem with this idiom for me is that it does not allow for
272             undefined or false values in the iterator. Of course, if this fits your
273             data, then there is no problem. Otherwise I would recommend the C/C
274             idiom instead.
275              
276             =cut
277              
278             sub get_next {
279 24     24 1 45 my ($self) = @_;
280 24         50 $self->{_iterated} = 1;
281 24 100       102 return undef unless ($self->{_current_index} < $self->{_length}); ## no critic: Subroutines::ProhibitExplicitReturnUndef
282 18         61 return $self->_getItem($self->{_iteratee}, $self->{_current_index}++);
283             }
284              
285             =head2 getNext
286              
287             Alternative name for get_next
288              
289             =cut
290              
291 49     49 1 12816 sub getNext { my $self = shift; $self->get_next(@_) }
  49         182  
292              
293             =head2 peek([$n])
294              
295             This method can be used to peek ahead at the next item in the iterator. It
296             is non-destructive, meaning it does not advance the internal pointer. If
297             this method is called and attempts to reach beyond the bounds of the iterator,
298             it will return undef.
299              
300             Takes an optional positive integer (E 0) that specifies how far ahead you want to peek:
301              
302             $i->peek(2); # gives you 2nd next element
303             $i->peek(10); # gives you 10th next element
304              
305             Note that C is the same as C.
306              
307             Throws an exception if C<$n> E= 0.
308              
309             B Before version 0.03 this method would throw an exception if called
310             out of bounds. I decided this was not a good practice, as it made it difficult
311             to be able to peek ahead effectively. This is not the case when calling with an argument
312             that is E= 0 though, as it's clearly a sign of incorrect usage.
313              
314             =cut
315              
316             sub peek {
317 35     35 1 677 my ($self, $n) = @_;
318              
319 35 100       130 if(not defined $n) { $n = 1 }
  22 100       38  
    100          
320 1         22 elsif(not $n) { die "peek(0) doesn't make sense, did you mean get_next()?" }
321 1         6 elsif($n < 0) { die "peek() with negative argument doesn't make sense, perhaps you should use a BiDirectional iterator" }
322              
323 33         77 my $idx = $self->{_current_index} + ($n - 1);
324              
325 33 100       156 return undef unless ($idx < $self->{_length}); ## no critic: Subroutines::ProhibitExplicitReturnUndef
326 26         80 return $self->_getItem($self->{_iteratee}, $idx);
327             }
328              
329             =head2 current
330              
331             This method can be used to get the current item in the iterator. It is non-destructive,
332             meaning that it does not advance the internal pointer. This value will match the
333             last value dispensed by C or C.
334              
335             =cut
336              
337             sub current {
338 24     24 1 62 my ($self) = @_;
339 24         84 return $self->_getItem($self->{_iteratee}, $self->currentIndex());
340             }
341              
342             =head2 current_index
343              
344             This method can be used to get the current index in the iterator. It is non-destructive,
345             meaning that it does not advance the internal pointer. This value will match the index
346             of the last value dispensed by C or C.
347              
348             =cut
349              
350             sub current_index {
351 68     68 1 800 my ($self) = @_;
352 68 100       349 return ($self->{_current_index} != 0) ? $self->{_current_index} - 1 : 0;
353             }
354              
355             =head2 currentIndex
356              
357             Alternative name for current_index
358              
359             =cut
360              
361 66     66 1 7635 sub currentIndex { my $self = shift; $self->current_index(@_) }
  66         199  
362              
363             =head2 reset
364              
365             Reset index to allow iteration from the start
366              
367             =cut
368              
369             sub reset
370             {
371 1     1 1 5 my $self = shift;
372 1         2 $self->{'_current_index'} = 0;
373             }
374              
375             =head2 get_length
376              
377             This is a basic accessor for getting the length of the array being iterated over.
378              
379             =cut
380              
381             sub get_length {
382 32     32 1 56 my $self = shift;
383              
384 32         107 return $self->{_length};
385             }
386              
387             =head2 getLength
388              
389             Alternative name for get_length
390              
391             =cut
392              
393 32     32 1 51 sub getLength { my $self = shift; $self->get_length(@_) }
  32         75  
394              
395             1;
396              
397             =head1 TODO
398              
399             =over 4
400              
401             =item Improve BiDirectional Test suite
402              
403             I want to test the back-and-forth a little more and make sure they work well with one another.
404              
405             =item Other Iterators
406              
407             Array::Iterator::BiDirectional::Circular, Array::Iterator::Skipable and
408             Array::Iterator::BiDirectional::Skipable are just a few ideas I have had. I am going
409             to hold off for now until I am sure they are actually useful.
410              
411             =back
412              
413             =head1 SEE ALSO
414              
415             This module now includes several subclasses of Array::Iterator which add certain behaviors
416             to Array::Iterator, they are:
417              
418             =over 4
419              
420             =item C
421              
422             Adds the ability to move backward and forward through the array.
423              
424             =item C
425              
426             When this iterator reaches the end of its list, it will loop back to the start again.
427              
428             =item C
429              
430             This iterator can be reset to its beginning and used again.
431              
432             =back
433              
434             The Design Patterns book by the Gang of Four, specifically the Iterator pattern.
435              
436             Some of the interface for this class is based on the Java Iterator interface.
437              
438             =head1 OTHER ITERATOR MODULES
439              
440             There are several on CPAN with the word Iterator in them.
441             Most of them are
442             actually iterators included inside other modules, and only really useful within that
443             parent module's context. There are, however, some other modules out there that are just
444             for pure iteration. I have provided a list below of the ones I have found if perhaps
445             you don't happen to like the way I do it.
446              
447             =over 4
448              
449             =item Tie::Array::Iterable
450              
451             This module ties the array, something we do not do. But it also makes an attempt to
452             account for, and allow the array to be changed during iteration. It accomplishes this
453             control because the underlying array is tied. As we all know, tie-ing things can be a
454             performance issue, but if you need what this module provides, then it will likely be
455             an acceptable compromise. Array::Iterator makes no attempt to deal with this mid-iteration
456             manipulation problem.
457             In fact,
458             it is recommended to not alter your array with Array::Iterator,
459             and if possible we will enforce this in later versions.
460              
461             =item Data::Iter
462              
463             This module allows for simple iteration over both hashes and arrays.
464             It does it by
465             importing several functions that can be used to loop over either type (hash or array)
466             in the same way. It is an interesting module, it differs from Array::Iterator in
467             paradigm (Array::Iterator is more OO) and intent.
468              
469             =item Class::Iterator
470              
471             This is essentially a wrapper around a closure-based iterator.
472             This method can be very
473             flexible, but at times is difficult to manage due to the inherent complexity of using
474             closures. I actually was a closure-as-iterator fan for a while but eventually moved
475             away from it in favor of the more plain vanilla means of iteration, like that found
476             Array::Iterator.
477              
478             =item Class::Iter
479              
480             This is part of the Class::Visitor module and is a Visitor and Iterator extension to
481             Class::Template.
482             Array::Iterator is a standalone module that is not associated with others.
483              
484             =item B
485              
486             Data::Iterator::EasyObj makes your array of arrays into iterator objects.
487             It also can
488             further nest additional data structures including Data::Iterator::EasyObj
489             objects.
490             Array::Iterator is one-dimensional only and does not attempt to do many of
491             the more advanced features of this module.
492              
493             =back
494              
495             =head1 ACKNOWLEDGEMENTS
496              
497             =over 4
498              
499             =item Thanks to Hugo Cornelis for pointing out a bug in C
500              
501             =item Thanks to Phillip Moore for providing the patch to allow single element iteration
502             through the hash-ref constructor parameter.
503              
504             =back
505              
506             =head1 ORIGINAL AUTHOR
507              
508             stevan little, Estevan@iinteractive.comE
509              
510             =head1 ORIGINAL COPYRIGHT AND LICENSE
511              
512             Copyright 2004, 2005 by Infinity Interactive, Inc.
513              
514             L
515              
516             This library is free software; you can redistribute it and/or modify
517             it under the same terms as Perl itself.
518              
519             =head1 PREVIOUS MAINTAINER
520              
521             Maintained 2017 to 2025 PERLANCAR
522              
523             =head1 SUPPORT
524              
525             This module is provided as-is without any warranty.
526              
527             Please report any bugs or feature requests to C,
528             or through the web interface at
529             L.
530             I will be notified, and then you'll
531             automatically be notified of progress on your bug as I make changes.
532              
533             You can find documentation for this module with the perldoc command.
534              
535             perldoc Array::Iterator
536              
537             You can also look for information at:
538              
539             =over 4
540              
541             =item * MetaCPAN
542              
543             L
544              
545             =item * RT: CPAN's request tracker
546              
547             L
548              
549             =item * CPAN Testers' Matrix
550              
551             L
552              
553             =item * CPAN Testers Dependencies
554              
555             L
556              
557             =back
558              
559             =cut