The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

# Data::PowerSet.pm
#
# Copyright (c) 2005-2008 David Landgren
# All rights reserved
use strict;
use vars qw/$VERSION @ISA @EXPORT_OK/;
$VERSION = '0.05';
@ISA = ('Exporter');
=head1 NAME
Data::PowerSet - Generate all subsets of a list of elements
=head1 VERSION
This document describes version 0.05 of Data::PowerSet, released
2008-05-13.
=head1 SYNOPSIS
use Data::PowerSet 'powerset';
my $powerset = powerset( 3, 1, 4 );
for my $p (@$powerset) {
print "@$p\n";
}
# prints
3 1 4
1 4
3 4
4
3 1
1
3
An object-oriented interface is also available;
my $d = Data::PowerSet->new( 3, 1, 4 );
while (my $r = $d->next) {
print "@$r\n";
}
# produces the same output as above
=head1 DESCRIPTION
C<Data::PowerSet> takes a list and returns all possible
combinations of the elements appearing in the list without replacement.
=head1 EXPORTABLE FUNCTIONS
=over 8
=item powerset
The C<powerset> function takes an array (or a reference to an array) on
input and returns a reference to an array of arrays containing all the
possible unique combinations of elements.
It is also possible to supply a reference to hash as the first
parameter to tweak the behaviour. See the C<new> method for a
description of what keys can be specified.
powerset( 2, 5, 10, 17 );
powerset( {min => 1}, qw(a b c d) );
powerset( [qw[ bodine mondaugen gadrulfi fleische eigenvalue ]] );
=cut
push @EXPORT_OK, 'powerset';
sub powerset {
my %args;
if (ref($_[0]) eq 'HASH') {
%args = %{shift @_};
}
my @list = ref($_[0]) eq 'ARRAY' ? @{shift @_} : @_;
$args{min} = exists $args{min} ? $args{min} < 0 ? 0 : $args{min} : 0;
$args{max} = exists $args{max} ? $args{max} > @list ? @list : $args{max} : @list;
($args{min}, $args{max}) = ($args{max}, $args{min})
if $args{max} < $args{min};
my $lim = 2 ** @list - 1;
my @powerset;
while( $lim >= 0 ) {
my @set;
my $mask = $lim--;
my $offset = 0;
while( $mask ) {
push @set, $list[$offset] if $mask & 1;
$mask >>= 1;
++$offset;
}
if( @set >= $args{min} and @set <= $args{max} ) {
push @powerset, exists $args{join}
? join( $args{join}, @set)
: [@set];
}
}
return \@powerset;
}
=back
=head1 METHODS
The object-oriented interface provided by the module is implemented
with the following methods.
=over 8
=item new
Creates a new C<Data::PowerSet> object.
my $ps = Data::PowerSet->new( qw( foo bar grault waldo ));
A reference to a hash may
be supplied, to change the way the object behaves.
=over 8
=item B<min>
Minimum number of elements present in the selection.
Note that the empty set (no elements) is quite valid, according to
the mathematical definition of a power set. If this is not what you
expect, setting C<min> to 1 will effectively cause the empty set to
be excluded from the result.
my $ps = Data::PowerSet->new( {min=>2}, 2, 3, 5, 8, 11 );
In the above object, no returned list will contain fewer
than 2 elements.
=item B<max>
Maximum number of elements present in the selection.
my $ps = Data::PowerSet->new( {max=>3}, 2, 3, 5, 8, 11 );
In the above object, no returned list will contain more
than 3 elements.
=item B<join>
Perform a C<join()> on each returned list using the
specified value.
my $ps = Data::Powerset->new( {join=>'-'}, 'a', 'b' );
When this attribute is used, the C<next()> method will
return a scalar rather than a reference to an array.
=back
=cut
sub new {
my $class = shift;
my %args;
if( ref($_[0]) eq 'HASH' ) {
%args = %{shift(@_)};
}
if( ref($_[0]) eq 'ARRAY' ) {
$args{data} = shift @_;
}
else {
$args{data} = [@_],
}
$args{current} = 2**@{$args{data}}-1;
$args{min} =
exists $args{min}
? $args{min} < 0
? 0 : $args{min}
: 0
;
$args{max} =
exists $args{max}
? $args{max} > @{$args{data}}
? @{$args{data}} : $args{max}
: @{$args{data}}
;
($args{min}, $args{max}) = ($args{max}, $args{min})
if $args{max} < $args{min};
return bless \%args, $class;
}
=item next
Returns a reference to an array containing the next combination of
elements from the original list;
my $ps = Data::PowerSet->new(qw(e t a i s o n));
my $first = $ps->next;
my $next = $ps->next;
=cut
sub next {
my $self = shift;
my $ok = 0;
my @set;
until( $ok ) {
return undef unless $self->{current} >= 0;
my $mask = $self->{current}--;
my $offset = 0;
@set = ();
while( $mask ) {
push @set, $self->{data}[$offset] if $mask & 1;
$mask >>= 1;
++$offset;
}
$ok = 1 if @set >= $self->{min} and @set <= $self->{max};
}
return exists $self->{join} ? join($self->{join}, @set) : \@set;
}
=item reset
Restart from the first combination of the list.
=cut
sub reset {
my $self = shift;
$self->{current} = 2**@{$self->{data}}-1;
}
=item data
Accept a new list of elements from which to draw combinations.
$ps->data( qw(all new elements to use) );
=cut
sub data {
my $self = shift;
$self->{data} = [@_],
$self->{current} = 2**@{$self->{data}}-1;
$self->{min} = @{$self->{data}} if $self->{min} > @{$self->{data}};
$self->{max} = @{$self->{data}} if $self->{max} > @{$self->{data}};
}
=item count
Returns the number of elements in the set. This can be used
to set C<max> to the number of elements minus one, in order to
exclude the set of all elements, when the number of elements
is difficult to determine beforehand.
=cut
sub count {
my $self = shift;
return scalar(@{$self->{data}});
}
=back
=head1 DIAGNOSTICS
None.
=head1 NOTES
Power sets grow exponentially. A power set of 10 elements returns
a more than one thousand results. A power set of 20 elements contains
more than one million results. The module is not expected to be put
to use in larger sets.
A power set, by definition, includes the set of no elements and
the set of all elements. If these results are not desired, the
C<min> and C<max> methods or properties can be used to exclude
them from the results.
This module works with perl version 5.005_04 and above.
=head1 SEE ALSO
=over 8
=item L<List::PowerSet>
Another module that generates power sets. If I had managed to find
it in a search beforehand, I probably would have used it instead.
Nonetheless, C<Data::PowerSet> has a couple of features not
present in C<List::PowerSet>, but otherwise both can be used
pretty much interchangeably.
=item L<Algorithm::Combinatorics>
A fast (no stacks, no recursion) method for generating permutations
and combinations of a set. A power set is merely the union of all
combinations (of differing lengths).
The wikipedia definition of a power set.
=back
=head1 BUGS
None known. Please report all bugs at
Make sure you include the output from the following two commands:
perl -MData::PowerSet -le 'print Data::PowerSet::VERSION'
perl -V
=head1 ACKNOWLEDGEMENTS
This module is dedicated to Estelle Souche, who pointed out the very
elegant and obvious algorithm. Smylers suggested the name.
=head1 AUTHOR
David Landgren, copyright (C) 2005-2008. All rights reserved.
If you (find a) use this module, I'd love to hear about it.
If you want to be informed of updates, send me a note. You
know my first name, you know my domain. Can you guess my
e-mail address?
=head1 LICENSE
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
'The Lusty Decadent Delights of Imperial Pompeii';