File Coverage

blib/lib/Set/Partition.pm
Criterion Covered Total %
statement 82 82 100.0
branch 28 28 100.0
condition 7 7 100.0
subroutine 7 7 100.0
pod 3 3 100.0
total 127 127 100.0


line stmt bran cond sub pod time code
1             # Set::Partition.pm
2             #
3             # Copyright (c) 2006 David Landgren
4             # All rights reserved
5              
6             package Set::Partition;
7 2     2   1178 use strict;
  2         4  
  2         56  
8              
9 2     2   9 use vars qw/$VERSION/;
  2         3  
  2         109  
10             $VERSION = '0.03';
11              
12 2     2   11 use constant DEBUG => 0; # if you want to see what's going on
  2         7  
  2         1921  
13              
14             =head1 NAME
15              
16             Set::Partition - Enumerate all arrangements of a set in fixed subsets
17              
18             =head1 VERSION
19              
20             This document describes version 0.03 of Set::Partition,
21             released 2006-10-11.
22              
23             =head1 SYNOPSIS
24              
25             use Set::Partition;
26              
27             my $s = Set::Partition->new(
28             list => [qw(a b c d e)],
29             partition => [2, 3],
30             );
31             while (my $p = $s->next) {
32             print join( ' ', map { "(@$_)" } @$p ), $/;
33             }
34             # produces
35             (a b) (c d e)
36             (a c) (b d e)
37             (a d) (b c e)
38             (a e) (b c d)
39             (b c) (a d e)
40             (b d) (a c e)
41             (b e) (a c d)
42             (c d) (a b e)
43             (c e) (a b d)
44             (d e) (a b c)
45              
46             # or with a hash
47             my $s = Set::Partition->new(
48             list => { b => 'bat', c => 'cat', d => 'dog' },
49             partition => [2, 1],
50             );
51             while (my $p = $s->next) {
52             ...
53             }
54              
55             =head1 DESCRIPTION
56              
57             C takes a list or hash of elements and a list
58             numbers that represent the sizes of the partitions into which the
59             list of elements should be arranged.
60              
61             The resulting object can then be used as an iterator which returns
62             a reference to an array of lists, that represents the original list
63             arranged according to the given partitioning. All possible arrangements
64             are returned, and the object returns C when the entire
65             combination space has been exhausted.
66              
67             =head1 METHODS
68              
69             =over 8
70              
71             =item new
72              
73             Creates a new C object. A set of key/value parameters
74             can be supplied to control the finer details of the object's
75             behaviour.
76              
77             B, the list of elements in the set.
78              
79             B, the list of integers representing the size of the
80             partitions used to arrange the set. The sum should be equal to the
81             number of elements given by B. If it less than the number of
82             elements, a dummy partition will be added to equalise the count.
83             This partition will be returned during iteration. If the sum is
84             greater than the number of elements, C will C with a
85             fatal error.
86              
87             =cut
88              
89             sub new {
90 8     8 1 49630 my $class = shift;
91 8         25 my %args = @_;
92 8   100     40 my $part = $args{partition} || [];
93 8         15 my $in = $args{list};
94 8         19 my $list;
95             my $val;
96 8 100       21 if ($in) {
97 7 100       23 if (ref($in) eq 'HASH') {
98 1         3 $list = [keys %$in];
99 1         4 $val = [values %$in];
100             }
101             else {
102 6         12 $list = $in;
103             }
104             }
105             else {
106 1         4 $list = [];
107             }
108 8         12 my $sum = 0;
109 8         30 $sum += $_ for @$part;
110 8 100       35 if ($sum > @$list) {
    100          
111 1         2 my $list_nr = @$list;
112 1         12 require Carp;
113 1         231 Carp::croak("sum of partitions ($sum) exceeds available elements ($list_nr)\n");
114             }
115             elsif ($sum < @$list) {
116 2         6 push @$part, @$list - $sum;
117             }
118              
119             bless {
120 7         67 list => $list,
121             val => $val,
122             part => $args{partition},
123             num => [0..$#$list],
124             },
125             $class;
126             }
127              
128             =item next
129              
130             Returns the next arrangement of subsets, or C when all arrangements
131             have been enumerated.
132              
133             =cut
134              
135             sub next {
136 82     82 1 32719 my $self = shift;
137 82         139 my $list = $self->{list};
138 82         103 my $state = $self->{state};
139 82 100       165 if ($state) {
140 74 100       134 return unless $self->_bump();
141             }
142             else {
143 8         11 my $s = 0;
144 8         11 push @$state, ($s++) x $_ for @{$self->{part}};
  8         42  
145 8 100 100     42 $state ||= [(0) x (@$list)] if @$list; # if no partition was given
146 8         19 $self->{state} = $state;
147             }
148 76         85 my $out;
149 76 100       151 if ($self->{val}) {
150 3         5 $out->[$state->[$_]]{$list->[$_]} = $self->{val}[$_] for @{$self->{num}};
  3         26  
151             }
152             else {
153 73         72 push @{$out->[$state->[$_]]}, $list->[$_] for @{$self->{num}};
  73         173  
  402         1086  
154             }
155 76         95 DEBUG and print "@{$self->{state}}\n";
156 76         185 return $out;
157             }
158              
159             sub _bump {
160 74     74   80 my $self = shift;
161 74         89 my $in = $self->{state};
162 74         124 my $end = $#$in;
163 74         104 my $off = $end-1;
164 74         84 my $inc = 0;
165 74         156 while ($off >= 0) {
166 155         175 my $sib = $off+1;
167 155 100       324 ++$inc if $in->[$off] > $in->[$sib];
168 155 100       292 if ($in->[$off] < $in->[$sib]) {
169 68 100       160 if ($in->[$sib] > 1+$in->[$off]) {
170             # find smallest in [$sib..$end] > $in->[$off];
171 38         42 my $next = @$in;
172 38         77 while (--$next) {
173 62 100       142 last if $in->[$next] > $in->[$off];
174             }
175 38         41 (@{$in}[$off, $next]) = (@{$in}[$next, $off]);
  38         64  
  38         68  
176 38         49 if (DEBUG) {
177             print "@$in (reverse @{$in}[$sib..$end] needed)\n"
178             if $sib < $end;
179             }
180 38 100       97 @{$in}[$sib..$end] = reverse @{$in}[$sib..$end]
  25         45  
  25         47  
181             if $sib < $end;
182             }
183             else {
184             # just have to flip the current and next
185 30         27 DEBUG and print +(' ' x ($off*2)) . "^ ^\n";
186 30         38 (@{$in}[$off, $sib]) = (@{$in}[$sib, $off]);
  30         48  
  30         51  
187 30         36 if (DEBUG) {
188             print "@$in (sort @{$in}[$sib..$end] needed d=$inc)\n"
189             if $sib < $end and $inc;
190             }
191             # have to sort
192 30 100 100     133 @{$in}[$sib..$end] = sort {$a <=> $b} @{$in}[$sib..$end]
  16         33  
  38         72  
  16         62  
193             if $sib < $end and $inc;
194             }
195 68         214 return 1;
196             }
197 87         164 --$off;
198             }
199 6         28 return 0;
200             }
201              
202             =item reset
203              
204             Resets the object, which causes it to enumerate the arrangements from the
205             beginning.
206              
207             $p->reset; # begin again
208              
209             =cut
210              
211             sub reset {
212 1     1 1 264 my $self = shift;
213 1         4 delete $self->{state};
214 1         2 return $self;
215             }
216              
217             =back
218              
219             =head1 DIAGNOSTICS
220              
221             =head2 sum of partitions (%d) exceeds available elements (%d)
222              
223             A list of partition sizes (for instance, 2, 3, 4) was given, along
224             with a list to partition (for instance, containing 8 elements),
225             however, the number of elements required to fill the different
226             partitions (9) exceeds the number available in the source list (8).
227              
228             =head1 NOTES
229              
230             The order within a set is unimportant, thus, if
231              
232             (a b) (c d)
233              
234             is produced, then the following arrangement will never be encountered:
235              
236             (a b) (d c)
237              
238             On the other hand, the order of the sets is important, which means
239             that the following arrangement I be encountered:
240              
241             (c d) (a b)
242              
243             =head1 SEE ALSO
244              
245             =over 8
246              
247             =item L
248              
249             Permutations, combinations, derangements and more; all you need
250             for your set transformations.
251              
252             =back
253              
254             =head1 BUGS
255              
256             Using a partition of length 0 is valid, although you get back an C,
257             rather than an empty array. This could be construed as a bug.
258              
259             Please report all bugs at
260             L
261              
262             Make sure you include the output from the following two commands:
263              
264             perl -MSet::Partition -le 'print Set::Partition::VERSION'
265             perl -V
266              
267             =head1 ACKNOWLEDGEMENTS
268              
269             Ken Williams suggested the possibility to use a hash as a source
270             for partitioning.
271              
272             =head1 AUTHOR
273              
274             David Landgren, copyright (C) 2006. All rights reserved.
275              
276             http://www.landgren.net/perl/
277              
278             If you (find a) use this module, I'd love to hear about it. If you
279             want to be informed of updates, send me a note. You know my first
280             name, you know my domain. Can you guess my e-mail address?
281              
282             =head1 LICENSE
283              
284             This library is free software; you can redistribute it and/or modify
285             it under the same terms as Perl itself.
286              
287             =cut
288              
289             'The Lusty Decadent Delights of Imperial Pompeii';
290             __END__