File Coverage

blib/lib/Algorithm/ChooseSubsets.pm
Criterion Covered Total %
statement 29 40 72.5
branch 8 16 50.0
condition 9 15 60.0
subroutine 5 6 83.3
pod 0 3 0.0
total 51 80 63.7


line stmt bran cond sub pod time code
1              
2             #
3             # Algorithm::ChooseSubsets by Brian Duggan
4             #
5             # Copyright (c) 2002 Brian Duggan. All rights reserved.
6             # This program is free software; you can redistribute it and/or modify it
7             # under the same terms as Perl itself.
8             #
9              
10             package Algorithm::ChooseSubsets;
11              
12              
13 1     1   711 use strict;
  1         2  
  1         40  
14 1     1   7 use warnings;
  1         1  
  1         32  
15 1     1   6 use Carp;
  1         5  
  1         725  
16              
17             our $VERSION = '0.02';
18              
19             =head1 NAME
20              
21             Algorithm::ChooseSubsets - OO interface to iterate through subsets of a list.
22              
23             =head1 SYNOPSIS
24              
25             use Algorithm::ChooseSubsets
26              
27             # Choose all subsets of a set
28             $i = new Algorithm::ChooseSubsets($n);
29             $i = new Algorithm::ChooseSubsets(\@set);
30             $i = new Algorithm::ChooseSubsets(set=>\@set);
31              
32             # Choose subsets of a fixed size $k
33             $i = new Algorithm::ChooseSubsets($n,$k);
34             $i = new Algorithm::ChooseSubsets(\@set,$k);
35             $i = new Algorithm::ChooseSubsets(set=>\@set, size=>$k);
36              
37             # Choose subsets of sizes greater than or equal to k
38             $i = new Algorithm::ChooseSubsets($n,$k,1);
39             $i = new Algorithm::ChooseSubsets(\@set,$k,1);
40             $i = new Algorithm::ChooseSubsets(set=>\@set, size=>$k, all=>1);
41              
42             while ($x = $i->next) {
43             # Do something with @$x
44             }
45              
46             $i->reset; # return to the first subset.
47              
48             =head1 DESCRIPTION
49              
50             "Subsets" in this context refers to lists with elements taken
51             from the original list, and in the same order as the elements in the
52             original list. After creating the object, subsequent calls to next()
53             will return the next such list in lexicographic order (where the alphabet
54             is the original list).
55              
56             If K is specified, only subsets of that size will be returned. If K
57             is omitted, all subsets will be returned, beginning with the empty set
58             and ending with the entire set. If the 'all' flag and a value for 'K' are
59             specified, subsets of size greater than or equal to K will be returned.
60              
61             If a number, N, is used instead of a list, the list is taken to
62             be [0..N-1].
63              
64             =head1 EXAMPLES
65              
66             # Print ab ac ad ae bc bd be cd ce de
67             $i = new Algorithm::ChooseSubsets([qw(a b c d e)],2);
68             print @$x," " while ($x = $i->next);
69              
70             # Print all 2,598,960 possible poker hands.
71             $i = new Algorithm::ChooseSubsets (\@cards, 5);
72             print @$hand,"\n" while ($hand = $i->next);
73              
74             # Print ::0:1:2:01:02:12:012
75             $i = new Algorithm::ChooseSubsets(3);
76             print ":",@$j while ($j = $i->next);
77              
78             =head1 NOTES
79              
80             For a fixed K, next() will return a value N! / (K! * [N-K]!) times.
81             For all subsets and a list of size N, it'll return a value 2**N times.
82              
83             =head1 AUTHOR
84              
85             Brian Duggan
86              
87             =head1 SEE ALSO
88              
89             perl(1).
90              
91             =cut
92              
93             sub new {
94 1     1 0 51 my $class = shift;
95 1         3 my %args;
96              
97 1 50       4 if (ref($_[0]) eq 'ARRAY') { # e.g. ( [0..9], 5)
    0          
98 1         38 %args = ( 'set' => $_[0], 'size' => $_[1], 'all' => $_[2] );
99             } elsif ($_[0] =~ /^\d+$/) { # e.g. ( 10, 5)
100 0         0 %args = ( 'set' => [ 0 .. $_[0]-1 ], 'size' => $_[1], 'all' => $_[2] );
101             } else { # ( set => [0..9], size => 5)
102 0         0 %args = @_;
103             }
104              
105 1 50       4 if (!defined($args{'size'})) {
106 0         0 $args{'size'} = 0;
107 0         0 $args{'all'} = 1;
108             }
109              
110             bless (+{
111 1         9 _size => ($args{'size'}), # size of the subsets we are returning
112             _original_size => ($args{'size'}), # ditto, for resetting purposes
113             _set => ($args{'set'} || croak "Missing set"), # the set
114 1   33     6 _n => scalar(@{$args{'set'}}), # size of the set
115             _c => undef, # Current indexes to return.
116             _all => $args{'all'} # whether to do all or just one K.
117             },$class);
118             }
119              
120             #
121             # return the next subset.
122             #
123             sub next {
124 11     11 0 53 my $self = shift;
125 11         24 my ($n, $k, $c, $set) = @$self{qw(_n _size _c _set)};
126              
127             # First one?
128 11 100       22 !defined($c) && return [ @$set[@{$self->{_c} = [0..$k-1]}]];
  1         8  
129              
130             # Last one?
131 10   66     43 my $last_one = (($k==0 && scalar(@$c)==0) || ($c->[0]==$n-$k));
132 10 100 66     23 return undef if $last_one && !$self->{'_all'};
133 9 50 33     22 if ($last_one && $self->{'_all'}) {
134 0         0 $self->{'_size'}++;
135 0 0       0 return undef if (++$k > $n);
136 0         0 return [ @$set[@{$self->{_c} = [0..$k-1]}]];
  0         0  
137             }
138              
139             # impossible?
140 9 50       18 return undef if ($k > $n);
141              
142             # Find the position to change.
143 9         10 my $p = $k - 1;
144 9   100     40 $p-- while ($p > 0 && $c->[$p] == $n-$k+$p);
145              
146             # Change that position, and all subsequent ones.
147 9         24 @$c[$p..$k-1] = ($c->[$p]+1 .. $c->[$p] + $k-$p);
148              
149             # Set the internal state, and return the values.
150 9         14 $self->{'_c'} = $c;
151 9         25 return [@$set[@$c]];
152             }
153              
154             #
155             # reset to the first subset.
156             #
157             sub reset {
158 0     0 0   my $self = shift;
159 0           $self->{_size} = $self->{_original_size};
160 0           $self->{_c} = undef;
161             }
162              
163             1;
164