File Coverage

blib/lib/Iterator/Flex/Permute.pm
Criterion Covered Total %
statement 129 139 92.8
branch 27 36 75.0
condition 9 22 40.9
subroutine 16 19 84.2
pod 1 3 33.3
total 182 219 83.1


line stmt bran cond sub pod time code
1             package Iterator::Flex::Permute;
2              
3             # ABSTRACT: Permute Iterator Class
4              
5 2     2   499934 use v5.28;
  2         9  
6 2     2   14 use strict;
  2         5  
  2         81  
7 2     2   14 use warnings;
  2         4  
  2         231  
8 2     2   1259 use experimental qw( signatures postderef declared_refs );
  2         7390  
  2         14  
9              
10             our $VERSION = '0.33';
11              
12 2     2   1452 use parent 'Iterator::Flex::Base';
  2         375  
  2         16  
13 2     2   167 use Iterator::Flex::Utils qw( :IterAttrs :IterStates throw_failure );
  2         4  
  2         496  
14 2     2   1471 use Iterator::Flex::Factory 'to_iterator';
  2         6  
  2         172  
15 2     2   16 use Scalar::Util;
  2         4  
  2         110  
16 2     2   10 use Ref::Util 'is_hashref', 'is_arrayref';
  2         3  
  2         121  
17              
18 2     2   14 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 4     4 1 434747 sub new ( $class, $array, $pars = {} ) {
  4         12  
  4         8  
  4         9  
  4         7  
56              
57 4 50       18 throw_failure( parameter => q{'array' argument must be an array} )
58             unless is_arrayref( $array );
59              
60 4 50       14 throw_failure( parameter => q{'pars' argument must be a hash} )
61             unless is_hashref( $pars );
62              
63 4         105 my %pars = $pars->%*;
64 4   33     23 my $k = delete $pars{k} // $array->@*;
65 4         10 my $n = $array->@*;
66              
67 4 50 33     75 defined $k && Scalar::Util::looks_like_number( $k ) && int( $k ) == $k && $k > 0
      33        
      33        
68             || throw_failure( parameter => 'k parameter is not a positive integer' );
69              
70 4 50       13 throw_failure( parameter => "size of subset (k = $k) > size of set ( $n )" )
71             if $k > $n;
72              
73 4         42 $class->SUPER::new( { k => $k, array => $array, }, \%pars );
74             }
75              
76              
77             my sub permutations;
78              
79 4     4 0 9 sub construct ( $class, $state ) {
  4         6  
  4         8  
  4         8  
80              
81 4 50       12 throw_failure( parameter => q{state must be a HASH reference} )
82             unless Ref::Util::is_hashref( $state );
83              
84 4         22 my ( $array, $k, $idx, $value, $c, $i, $restart ) = @{$state}{
85 4         12 qw[ array k idx value
86             c i restart
87             ] };
88              
89 4   50     25 $value //= [];
90              
91 4   50     26 $c //= [ ( 0 ) x $k ];
92 4   50     18 $i //= 1;
93 4   50     20 $restart //= !!0;
94              
95 4         10 my $self;
96             my $iterator_state;
97 4         9 my $n = $array->@*;
98              
99             my %params = (
100              
101             ( +_SELF ) => \$self,
102              
103             ( +STATE ) => \$iterator_state,
104              
105              
106             ( +CURRENT ) => sub {
107 0 0   0   0 return $idx ? [ $value->@* ] : undef;
108             },
109              
110             ( +RESET ) => sub {
111 0     0   0 $i = 1;
112 0         0 $c = [ ( 0 ) x $k ];
113 0         0 $restart = !!0;
114              
115 0         0 $idx = undef;
116 0         0 $value->@* = ();
117             },
118              
119             +( REWIND ) => sub {
120 0     0   0 $i = 1;
121 0         0 $c = [ ( 0 ) x $k ];
122 0         0 $restart = !!0;
123 0         0 $idx = undef;
124             },
125              
126 4         60 );
127              
128 4 100       14 if ( $n == $k ) {
129              
130             $params{ +NEXT } = sub {
131 10 50   10   304 return $self->signal_exhaustion
132             if $iterator_state == IterState_EXHAUSTED;
133              
134 10         21 my \@value = $value;
135 10         16 my \@array = $array;
136              
137 10 100       26 if ( !defined $idx ) {
138 2         9 $idx = [ 0 .. $k - 1 ];
139             }
140             else {
141 8         25 $i = permutations( $c, $i, $n, $idx, $restart );
142 8 100       43 return $self->signal_exhaustion if !defined $i;
143 6         63 $restart = !!1;
144             }
145 8         27 @value = @array[ $idx->@* ];
146 8         36 return [@value];
147 2         16 };
148              
149             }
150              
151             else {
152 2         4 my $combination = @{$state}{'combination'};
  2         6  
153 2   50     14 $combination //= [ 0 .. $k - 1 ];
154              
155 2         6 my \@combination = $combination;
156              
157             $params{ +NEXT } = sub {
158              
159 20 50   20   208 return $self->signal_exhaustion
160             if $iterator_state == IterState_EXHAUSTED;
161              
162 20         37 my \@value = $value;
163 20         36 my \@array = $array;
164              
165 20 100       44 if ( !defined $idx ) {
166 2         7 $idx = [ 0 .. $k - 1 ];
167             }
168             else {
169 18         36 $i = permutations( $c, $i, $k, $idx, $restart );
170 18 100       42 if ( !defined $i ) {
171 9 100       25 next_combination( \@combination, $n )
172             or return $self->signal_exhaustion;
173 7         15 $i = 1;
174 7         21 $c = [ ( 0 ) x $k ];
175 7         14 $restart = !!0;
176 7         34 $idx = [ 0 .. $k - 1 ];
177             }
178             else {
179 9         16 $restart = !!1;
180             }
181             }
182 18         47 @value = @array[ @combination[ $idx->@* ] ];
183 18         53 return [@value];
184 2         15 };
185             }
186              
187 4         26 return \%params;
188             }
189              
190              
191             # https://en.wikipedia.org/wiki/Heap%27s_algorithm
192              
193             # procedure permutations(n : integer, A : array of any):
194             # // c is an encoding of the stack state.
195             # // c[k] encodes the for-loop counter for when permutations(k + 1, A) is called
196             # c : array of int
197             #
198             # for i := 0; i < n; i += 1 do
199             # c[i] := 0
200             # end for
201             #
202             # output(A)
203             #
204             # // i acts similarly to a stack pointer
205             # i := 1;
206             # while i < n do
207             # if c[i] < i then
208             # if i is even then
209             # swap(A[0], A[i])
210             # else
211             # swap(A[c[i]], A[i])
212             # end if
213             # output(A)
214             # // Swap has occurred ending the while-loop. Simulate the
215             # // increment of the while-loop counter
216             # c[i] += 1
217             # // Simulate recursive call reaching the base case by
218             # // bringing the pointer to the base case analog in the
219             # // array
220             # i := 1
221             # else
222             # // Calling permutations(i+1, A) has ended as the
223             # // while-loop terminated. Reset the state and simulate
224             # // popping the stack by incrementing the pointer.
225             # c[i] := 0
226             # i += 1
227             # end if
228             # end while
229              
230             #
231 26     26   44 sub permutations ( $c, $i, $n, $A, $restart ) {
  26         39  
  26         45  
  26         37  
  26         45  
  26         58  
  26         36  
232              
233             # these are initialized outside of here.
234 26         43 my \@c = $c;
235 26         40 my \@A = $A;
236              
237 26         58 while ( $i < $n ) {
238 44 100       98 if ( $c[$i] < $i ) {
239              
240 30 100       62 if ( $restart ) {
241 15         27 $restart = !!0;
242 15         30 $c[$i] += 1;
243 15         23 $i = 1;
244 15         38 next;
245             }
246              
247 15 100       37 if ( 0 == ( $i % 2 ) ) {
248 2         121 my $t = $A[$i];
249 2         7 $A[$i] = $A[0];
250 2         4 $A[0] = $t;
251             }
252             else {
253 13         25 my $t = $A[$i];
254 13         27 $A[$i] = $A[ $c[$i] ];
255 13         27 $A[ $c[$i] ] = $t;
256             }
257 15         61 return $i;
258              
259             }
260             else {
261 14         26 $c[$i] = 0;
262 14         33 $i++;
263             }
264             }
265              
266 11         25 return undef;
267             }
268              
269             # see https://cs.stackexchange.com/a/161542
270              
271 9     9 0 130 sub next_combination ( $A, $n ) {
  9         17  
  9         15  
  9         13  
272              
273 9         16 my \@A = $A;
274 9         19 my $k = @A;
275              
276             # for ( my $i = $k - 1 ; $i >= 0 ; $i-- ) {
277 9         22 for my $i ( reverse 0 .. $k - 1 ) {
278 14 100       38 if ( $A[$i] < $n - $k + $i ) {
279 7         12 $A[$i]++;
280 7         23 for my $j ( $i + 1 .. $k - 1 ) {
281 3         8 $A[$j] = $A[ $j - 1 ] + 1;
282             }
283 7         29 return !!1;
284             }
285             }
286 2         14 return !!0;
287             }
288              
289              
290             # Brute Force, slow
291              
292             # my \@idx = $idx;
293              
294             # my $last_slot;
295             # my $overflow;
296             # BACKWARDS:
297             # for my $slot ( reverse 0 .. $k - 1 ) {
298             # $last_slot = $slot;
299             # my $iarr = $idx[$slot];
300              
301             # $overflow = !!0;
302             # while ( $iarr++ < $n - 1 ) {
303             # next if elem_num( $iarr, @idx[ 0 .. $slot - 1 ] );
304             # $idx[$slot] = $iarr;
305             # last BACKWARDS;
306             # }
307             # $overflow = !!1;
308             # }
309              
310             # return $self->signal_exhaustion if $last_slot == 0 && $overflow;
311              
312             # FORWARDS:
313             # for my $slot ( $last_slot + 1 .. $k - 1 ) {
314             # for my $iarr ( 0 .. $n - 1 ) {
315             # next if elem_num( $iarr, @idx[ 0 .. $slot - 1 ] );
316             # $idx[$slot] = $iarr;
317             # next FORWARDS;
318             # }
319             # }
320              
321              
322             __PACKAGE__->_add_roles( qw[
323             State::Closure
324             Next::ClosedSelf
325             Rewind::Closure
326             Reset::Closure
327             Current::Closure
328             ] );
329              
330              
331             1;
332              
333             #
334             # This file is part of Iterator-Flex
335             #
336             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
337             #
338             # This is free software, licensed under:
339             #
340             # The GNU General Public License, Version 3, June 2007
341             #
342              
343             __END__