File Coverage

blib/lib/Iterator/Flex/Product.pm
Criterion Covered Total %
statement 105 106 99.0
branch 30 38 78.9
condition 4 5 80.0
subroutine 18 19 94.7
pod 1 2 50.0
total 158 170 92.9


line stmt bran cond sub pod time code
1             package Iterator::Flex::Product;
2              
3             # ABSTRACT: An iterator which produces a Cartesian product of iterators
4              
5 2     2   250096 use v5.28;
  2         8  
6 2     2   9 use strict;
  2         4  
  2         37  
7 2     2   5 use warnings;
  2         3  
  2         138  
8 2     2   417 use experimental qw( signatures declared_refs refaliasing );
  2         1735  
  2         13  
9              
10             our $VERSION = '0.33';
11              
12 2     2   1055 use Iterator::Flex::Utils qw( RETURN STATE EXHAUSTION :IterAttrs :IterStates can_meth );
  2         5  
  2         542  
13 2     2   585 use Iterator::Flex::Factory 'to_iterator';
  2         6  
  2         215  
14 2     2   18 use parent 'Iterator::Flex::Base';
  2         7  
  2         20  
15 2     2   231 use Ref::Util;
  2         4  
  2         95  
16 2     2   14 use List::Util;
  2         2  
  2         143  
17              
18 2     2   12 use namespace::clean;
  2         4  
  2         15  
19              
20              
21              
22              
23              
24              
25              
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38              
39              
40              
41              
42              
43              
44              
45              
46              
47              
48              
49              
50              
51              
52              
53              
54              
55              
56              
57              
58              
59              
60              
61              
62 6     6 1 29 sub new ( $class, @args ) {
  6         16  
  6         17  
  6         11  
63 6 50       23 my $pars = Ref::Util::is_hashref( $args[-1] ) ? pop @args : {};
64              
65 6 50       23 throw_failure( parameter => 'not enough parameters' )
66             unless @args;
67              
68 6         13 my @iterators;
69             my @keys;
70              
71             # distinguish between ( key => iterator, key =>iterator ) and ( iterator, iterator );
72 6 100       20 if ( Ref::Util::is_ref( $args[0] ) ) {
73 4         14 @iterators = @args;
74             }
75             else {
76 2 50       11 throw_failure( parameter => 'expected an even number of arguments' )
77             if @args % 2;
78              
79 2         7 while ( @args ) {
80 4         7 push @keys, shift @args;
81 4         14 push @iterators, shift @args;
82             }
83             }
84              
85 6         61 $class->SUPER::new( { keys => \@keys, depends => \@iterators, value => [] }, $pars );
86             }
87              
88 8     8 0 15 sub construct ( $class, $state ) { ## no critic (ExcessComplexity)
  8         16  
  8         14  
  8         14  
89 8 50       51 throw_failure( parameter => q{state must be a HASH reference} )
90             unless Ref::Util::is_hashref( $state );
91              
92 8   100     33 $state->{value} //= [];
93              
94             my ( \@depends, \@keys, \@value, $thaw )
95 8         59 = @{$state}{qw[ depends keys value thaw ]};
  8         55  
96              
97             # transform into iterators if required.
98             my @iterators
99 8         26 = map { to_iterator( $_, { ( +EXHAUSTION ) => RETURN } ) } @depends;
  16         86  
100              
101             # can only work if the iterators support a rewind method
102             throw_failure( parameter => q{all iterables must provide a rewind method} )
103 8 50   16   63 unless List::Util::all { defined can_meth( $_, 'rewind' ) } @iterators;
  16         52  
104              
105 8 50 66     52 throw_failure( parameter => q{number of keys not equal to number of iterators} )
106             if @keys && @keys != @iterators;
107              
108 8 100       30 @value = map { $_->current } @iterators
  4         17  
109             if $thaw;
110              
111             ## no critic ( AmbiguousNames )
112 8         22 my @set = ( 1 ) x @value;
113              
114 8         18 my @src = map { [ $_, $_->can( 'is_exhausted' ) ] } @iterators;
  16         93  
115              
116 8         19 my $self;
117             my $iterator_state;
118             my %params = (
119              
120             ( +_SELF ) => \$self,
121              
122             ( +STATE ) => \$iterator_state,
123              
124             ( +NEXT ) => sub {
125 44 100   44   124 return $self->signal_exhaustion if $iterator_state == IterState_EXHAUSTED;
126              
127             # first time through
128 33 100       74 if ( !@value ) {
129              
130 7         24 for my $src ( @src ) {
131 14         33 my ( $iter, $is_exhausted ) = $src->@*;
132 14         95 push @value, $iter->();
133              
134 14 50       40 return $self->signal_exhaustion
135             if $iter->$is_exhausted;
136             }
137              
138 7         27 @set = ( 1 ) x @value;
139             }
140              
141             else {
142 26         64 my ( $iter, $is_exhausted ) = $src[-1]->@*;
143              
144 26         63 $value[-1] = $iter->();
145 26 100       63 if ( $iter->$is_exhausted ) {
146 13         27 $set[-1] = 0;
147 13         55 my $idx = @src - 1;
148              
149 13         57 while ( --$idx >= 0 ) {
150 13         35 ( $iter, $is_exhausted ) = $src[$idx]->@*;
151 13         34 $value[$idx] = $iter->();
152 13 100       44 last unless $iter->$is_exhausted;
153 7         22 $set[$idx] = 0;
154             }
155              
156 13 100       68 return $self->signal_exhaustion
157             if !$set[0];
158              
159 6         39 while ( ++$idx < @src ) {
160 6         38 $src[$idx][0]->rewind;
161 6         15 $value[$idx] = $src[$idx][0]->();
162 6         20 $set[$idx] = 1;
163             }
164             }
165              
166             }
167 26 100       61 if ( @keys ) {
168 8         32 my %value;
169 8         30 @value{@keys} = @value;
170 8         40 return \%value;
171             }
172             else {
173 18         82 return [@value];
174             }
175             },
176              
177             ( +CURRENT ) => sub {
178 33 100   33   97 return undef if !@value;
179 27 100       80 return $self->signal_exhaustion if $iterator_state eq IterState_EXHAUSTED;
180 22 100       39 if ( @keys ) {
181 8         16 my %value;
182 8         21 @value{@keys} = @value;
183 8         27 return \%value;
184             }
185             else {
186 14         49 return [@value];
187             }
188             },
189              
190 0     0   0 ( +RESET ) => sub { @value = () },
191 1     1   4 ( +REWIND ) => sub { @value = () },
192 8         221 ( +_DEPENDS ) => \@iterators,
193             );
194              
195             # can only freeze if the iterators support a current method
196 8 50       56 if (
197 16     16   45 List::Util::all { defined can_meth( $_, 'current' ) }
198             @iterators
199             )
200             {
201              
202             $params{ +FREEZE } = sub {
203 2     2   12 return [ $class, { keys => \@keys } ];
204 8         36 };
205 8         27 $params{ +_ROLES } = ['Freeze'];
206             }
207              
208 8         35 $params{ +_NAME } = 'iproduct';
209 8         52 return \%params;
210             }
211              
212              
213             __PACKAGE__->_add_roles( qw[
214             State::Closure
215             Next::ClosedSelf
216             Current::Closure
217             Reset::Closure
218             Rewind::Closure
219             ] );
220              
221             1;
222              
223             #
224             # This file is part of Iterator-Flex
225             #
226             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
227             #
228             # This is free software, licensed under:
229             #
230             # The GNU General Public License, Version 3, June 2007
231             #
232              
233             __END__