File Coverage

blib/lib/Algorithm/Combinatorics.pm
Criterion Covered Total %
statement 187 217 86.1
branch 76 96 79.1
condition 12 15 80.0
subroutine 40 44 90.9
pod 9 9 100.0
total 324 381 85.0


line stmt bran cond sub pod time code
1             package Algorithm::Combinatorics;
2              
3 15     15   479428 use 5.006002;
  15         59  
  15         675  
4 15     15   90 use strict;
  15         29  
  15         747  
5              
6             our $VERSION = '0.27';
7              
8 15     15   86 use XSLoader;
  15         44  
  15         645  
9             XSLoader::load('Algorithm::Combinatorics', $VERSION);
10              
11 15     15   88 use Carp;
  15         27  
  15         1277  
12 15     15   89 use Scalar::Util qw(reftype);
  15         38  
  15         1591  
13 15     15   83 use Exporter;
  15         23  
  15         610  
14 15     15   78 use base 'Exporter';
  15         22  
  15         78634  
15             our @EXPORT_OK = qw(
16             combinations
17             combinations_with_repetition
18             variations
19             variations_with_repetition
20             tuples
21             tuples_with_repetition
22             permutations
23             circular_permutations
24             derangements
25             complete_permutations
26             partitions
27             subsets
28             );
29              
30             our %EXPORT_TAGS = (all => [ @EXPORT_OK ]);
31              
32              
33             sub combinations {
34 69     69 1 40984 my ($data, $k) = @_;
35 69         137 __check_params($data, $k);
36              
37 66 50       134 return __contextualize(__null_iter()) if $k < 0;
38 66 100       140 return __contextualize(__once_iter()) if $k == 0;
39 50 50       103 if ($k > @$data) {
40 0         0 carp("Parameter k is greater than the size of data");
41 0         0 return __contextualize(__null_iter());
42             }
43              
44 50         135 my @indices = 0..($k-1);
45             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
46 31176 100   31176   88121 __next_combination(\@indices, @$data-1) == -1 ? undef : [ @{$data}[@indices] ];
  31126         107915  
47 50         274 }, [ @{$data}[@indices] ]);
  50         198  
48              
49 50         106 return __contextualize($iter);
50             }
51              
52              
53             sub combinations_with_repetition {
54 37     37 1 58998 my ($data, $k) = @_;
55 37         104 __check_params($data, $k);
56              
57 34 50       160 return __contextualize(__null_iter()) if $k < 0;
58 34 100       78 return __contextualize(__once_iter()) if $k == 0;
59              
60 26         70 my @indices = (0) x $k;
61             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
62 24148 100   24148   60853 __next_combination_with_repetition(\@indices, @$data-1) == -1 ? undef : [ @{$data}[@indices] ];
  24122         67254  
63 26         118 }, [ @{$data}[@indices] ]);
  26         106  
64              
65 26         70 return __contextualize($iter);
66             }
67              
68             sub subsets {
69 46     46 1 48618 my ($data, $k) = @_;
70 46         115 __check_params($data, $k, 1);
71              
72 42 100       100 return combinations($data, $k) if defined $k;
73              
74 9         10 my $finished = 0;
75 9         24 my @odometer = (1) x @$data;
76             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
77 65603 100   65603   133196 return if $finished;
78 65594         301568 my $subset = __next_subset($data, \@odometer);
79 65594 100       151621 $finished = 1 if @$subset == 0;
80 65594         152749 $subset;
81 9         51 });
82              
83 9         20 return __contextualize($iter);
84             }
85              
86             sub variations {
87 72     72 1 162207 my ($data, $k) = @_;
88 72         184 __check_params($data, $k);
89              
90 66 50       134 return __contextualize(__null_iter()) if $k < 0;
91 66 100       139 return __contextualize(__once_iter()) if $k == 0;
92 50 50       103 if ($k > @$data) {
93 0         0 carp("Parameter k is greater than the size of data");
94 0         0 return __contextualize(__null_iter());
95             }
96              
97             # permutations() is more efficient because it knows
98             # all indices are always used
99 50 100       124 return permutations($data) if @$data == $k;
100              
101 34         123 my @indices = 0..($k-1);
102 34         110 my @used = ((1) x $k, (0) x (@$data-$k));
103             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
104 30816 100   30816   92778 __next_variation(\@indices, \@used, @$data-1) == -1 ? undef : [ @{$data}[@indices] ];
  30782         91507  
105 34         223 }, [ @{$data}[@indices] ]);
  34         167  
106              
107 34         84 return __contextualize($iter);
108             }
109             *tuples = \&variations;
110              
111              
112             sub variations_with_repetition {
113 74     74 1 195530 my ($data, $k) = @_;
114 74         203 __check_params($data, $k);
115              
116 68 50       150 return __contextualize(__null_iter()) if $k < 0;
117 68 100       154 return __contextualize(__once_iter()) if $k == 0;
118              
119 52         137 my @indices = (0) x $k;
120             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
121 67086 100   67086   175338 __next_variation_with_repetition(\@indices, @$data-1) == -1 ? undef : [ @{$data}[@indices] ];
  67034         177073  
122 52         279 }, [ @{$data}[@indices] ]);
  52         243  
123              
124 52         133 return __contextualize($iter);
125             }
126             *tuples_with_repetition = \&variations_with_repetition;
127              
128              
129             sub __variations_with_repetition_gray_code {
130 0     0   0 my ($data, $k) = @_;
131 0         0 __check_params($data, $k);
132              
133 0 0       0 return __contextualize(__null_iter()) if $k < 0;
134 0 0       0 return __contextualize(__once_iter()) if $k == 0;
135              
136 0         0 my @indices = (0) x $k;
137 0         0 my @focus_pointers = 0..$k; # yeah, length $k+1
138 0         0 my @directions = (1) x $k;
139             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
140 0         0 __next_variation_with_repetition_gray_code(
141             \@indices,
142             \@focus_pointers,
143             \@directions,
144             @$data-1,
145 0 0   0   0 ) == -1 ? undef : [ @{$data}[@indices] ];
146 0         0 }, [ @{$data}[@indices] ]);
  0         0  
147              
148 0         0 return __contextualize($iter);
149             }
150              
151              
152             sub permutations {
153 27     27 1 9465 my ($data) = @_;
154 27         195 __check_params($data, 0);
155              
156 25 100       54 return __contextualize(__once_iter()) if @$data == 0;
157              
158 23         63 my @indices = 0..(@$data-1);
159             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
160 40362 100   40362   118123 __next_permutation(\@indices) == -1 ? undef : [ @{$data}[@indices] ];
  40339         128836  
161 23         111 }, [ @{$data}[@indices] ]);
  23         105  
162              
163 23         48 return __contextualize($iter);
164             }
165              
166              
167             sub circular_permutations {
168 11     11 1 11312 my ($data) = @_;
169 11         27 __check_params($data, 0);
170              
171 11 100       25 return __contextualize(__once_iter()) if @$data == 0;
172 9 100 100     43 return __contextualize(__once_iter([@$data])) if @$data == 1 || @$data == 2;
173              
174 5         14 my @indices = 1..(@$data-1);
175             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
176 40336 100   40336   112220 __next_permutation(\@indices) == -1 ? undef : [ @{$data}[0, @indices] ];
  40331         132869  
177 5         23 }, [ @{$data}[0, @indices] ]);
  5         38  
178              
179 5         11 return __contextualize($iter);
180             }
181              
182             sub __permutations_heap {
183 0     0   0 my ($data) = @_;
184 0         0 __check_params($data, 0);
185              
186 0 0       0 return __contextualize(__once_iter()) if @$data == 0;
187              
188 0         0 my @a = 0..(@$data-1);
189 0         0 my @c = (0) x (@$data+1); # yeah, there's an spurious $c[0] to make the notation coincide
190             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
191 0 0   0   0 __next_permutation_heap(\@a, \@c) == -1 ? undef : [ @{$data}[@a] ];
  0         0  
192 0         0 }, [ @{$data}[@a] ]);
  0         0  
193              
194 0         0 return __contextualize($iter);
195             }
196              
197              
198             sub derangements {
199 28     28 1 27158 my ($data) = @_;
200 28         70 __check_params($data, 0);
201              
202 24 100       59 return __contextualize(__once_iter()) if @$data == 0;
203 20 100       52 return __contextualize(__null_iter()) if @$data == 1;
204              
205 16         93 my @indices = 0..(@$data-1);
206 16         55 @indices[$_, $_+1] = @indices[$_+1, $_] for map { 2*$_ } 0..((@$data-2)/2);
  32         121  
207 16 100       67 @indices[-1, -2] = @indices[-2, -1] if @$data % 2;
208             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
209 296706 100   296706   966087 __next_derangement(\@indices) == -1 ? undef : [ @{$data}[@indices] ];
  296690         976728  
210 16         70 }, [ @{$data}[@indices] ]);
  16         80  
211              
212 16         39 return __contextualize($iter);
213             }
214              
215             *complete_permutations = \&derangements;
216              
217              
218             sub partitions {
219 42     42 1 1706702 my ($data, $k) = @_;
220 42 100       102 if (defined $k) {
221 26         50 __partitions_of_size_p($data, $k);
222             } else {
223 16         35 __partitions_of_all_sizes($data);
224             }
225             }
226              
227             sub __partitions_of_all_sizes {
228 16     16   22 my ($data) = @_;
229 16         36 __check_params($data, 0);
230              
231 12 100       34 return __contextualize(__once_iter()) if @$data == 0;
232              
233 10         26 my @k = (0) x @$data;
234 10         21 my @M = (0) x @$data;
235             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
236 137168 100   137168   479571 __next_partition(\@k, \@M) == -1 ? undef : __slice_partition(\@k, \@M, $data);
237 10         54 }, __slice_partition(\@k, \@M, $data));
238              
239 10         20 return __contextualize($iter);
240             }
241              
242             # We use @k and $p here and sacrifice the uniform usage of $k
243             # to follow the notation in [3].
244             sub __partitions_of_size_p {
245 26     26   40 my ($data, $p) = @_;
246 26         46 __check_params($data, $p);
247              
248 26 50       54 return __contextualize(__null_iter()) if $p < 0;
249 26 100 66     74 return __contextualize(__once_iter()) if @$data == 0 && $p == 0;
250 22 100       47 return __contextualize(__null_iter()) if $p == 0;
251              
252 18 50       37 if ($p > @$data) {
253 0         0 carp("Parameter k is greater than the size of data");
254 0         0 return __contextualize(__null_iter());
255             }
256              
257 18         55 my $q = @$data - $p + 1;
258 18         38 my @k = (0) x $q;
259 18         27 my @M = (0) x $q;
260 18         84 push @k, $_ - $q + 1 for $q..(@$data-1);
261 18         45 push @M, $_ - $q + 1 for $q..(@$data-1);
262             my $iter = Algorithm::Combinatorics::Iterator->new(sub {
263 179891 100   179891   746553 __next_partition_of_size_p(\@k, \@M, $p) == -1 ? undef : __slice_partition_of_size_p(\@k, $p, $data);
264 18         95 }, __slice_partition_of_size_p(\@k, $p, $data));
265              
266 18         34 return __contextualize($iter);
267             }
268              
269              
270             sub __slice_partition {
271 137168     137168   158378 my ($k, $M, $data) = @_;
272 137168         161854 my @partition = ();
273 137168         172029 my $size = $M->[-1] + 1; # $M->[0] is always 0 in our code
274 137168         616676 push @partition, [] for 1..$size;
275 137168         166475 my $i = 0;
276 137168         183527 foreach my $x (@$data) {
277 1350233         1154058 push @{$partition[$k->[$i]]}, $x;
  1350233         1806992  
278 1350233         1545103 ++$i;
279             }
280 137168         397488 return \@partition;
281             }
282              
283             # We use @k and $p here and sacrifice the uniform usage of $k
284             # to follow the notation in [3].
285             sub __slice_partition_of_size_p {
286 179891     179891   258398 my ($k, $p, $data) = @_;
287 179891         235525 my @partition = ();
288 179891         801607 push @partition, [] for 1..$p;
289 179891         279873 my $i = 0;
290 179891         254780 foreach my $x (@$data) {
291 1944420         2115178 push @{$partition[$k->[$i]]}, $x;
  1944420         2916746  
292 1944420         2587411 ++$i;
293             }
294 179891         569278 return \@partition;
295             }
296              
297              
298             sub __check_params {
299 406     406   629 my ($data, $k, $k_is_not_required) = @_;
300 406 100       1329 if (not defined $data) {
301 13         2624 croak("Missing parameter data");
302             }
303 393 100 100     1745 unless ($k_is_not_required || defined $k) {
304 6         2625 croak("Missing parameter k");
305             }
306              
307 387         1130 my $type = reftype $data;
308 387 100 66     1791 if (!defined($type) || $type ne "ARRAY") {
309 13         1778 croak("Parameter data is not an arrayref");
310             }
311              
312 374 50 66     1832 carp("Parameter k is negative") if !$k_is_not_required && $k < 0;
313             }
314              
315              
316             # Given an iterator that responds to the next() method this
317             # subrutine returns the iterator in scalar context, loops
318             # over the iterator to build and return an array of results
319             # in list context, and does nothing but issue a warning in
320             # void context.
321             sub __contextualize {
322 325     325   408 my $iter = shift;
323 325         456 my $w = wantarray;
324 325 50       554 if (defined $w) {
325 325 100       548 if ($w) {
326 152         249 my @result = ();
327 152         414 while (my $c = $iter->next) {
328 956         2198 push @result, $c;
329             }
330 152         1425 return @result;
331             } else {
332 173         727 return $iter;
333             }
334             } else {
335 0         0 my $sub = (caller(1))[3];
336 0         0 carp("Useless use of $sub in void context");
337             }
338             }
339              
340             sub __null_iter {
341 8     8   27 return Algorithm::Combinatorics::Iterator->new(sub { return });
  8     8   45  
342             }
343              
344              
345             sub __once_iter {
346 74     74   101 my $tuple = shift;
347 4     4   10 $tuple ? Algorithm::Combinatorics::Iterator->new(sub { return }, $tuple) :
348 74 100   70   1181 Algorithm::Combinatorics::Iterator->new(sub { return }, []);
  70         321  
349             }
350              
351              
352              
353             # This is a bit dirty by now, the objective is to be able to
354             # pass an initial sequence to the iterator and avoid a test
355             # in each iteration saying whether the sequence was already
356             # returned or not, since that might potentially be done a lot
357             # of times.
358             #
359             # The solution is to return an iterator that has a first sequence
360             # associated. The first time you call it that sequence is returned
361             # and the iterator rebless itself to become just a wrapped coderef.
362             #
363             # Note that the public contract is that responds to next(), no
364             # iterator class name is documented.
365             package Algorithm::Combinatorics::Iterator;
366              
367             sub new {
368 325     325   515 my ($class, $coderef, $first_seq) = @_;
369 325 100       632 if (defined $first_seq) {
370 308         1394 return bless [$coderef, $first_seq], $class;
371             } else {
372 17         73 return bless $coderef, 'Algorithm::Combinatorics::JustCoderef';
373             }
374             }
375              
376             sub next {
377 308     308   1006 my ($self) = @_;
378 308         583 $_[0] = $self->[0];
379 308         707 bless $_[0], 'Algorithm::Combinatorics::JustCoderef';
380 308         979 return $self->[1];
381             }
382              
383             package Algorithm::Combinatorics::JustCoderef;
384              
385             sub next {
386 913374     913374   4099987 my ($self) = @_;
387 913374         1485085 return $self->();
388             }
389              
390              
391             1;
392              
393             __END__