Perl Monk, Perl Meditation PerlMonks

### Multiple Combinatorics

by aartist (Monk)
 on Apr 15, 2012 at 07:14 UTC Need Help??
aartist has asked for the wisdom of the Perl Monks concerning the following question:

I am looking for combinations from multiple sets. For exmaple, I need 3 letters from 'a'..'z' and 2 digits from '0'..'9'. I can get single combination with Math::Combinatorics easily. Is there one for multiple set? ( Above sets are just examples, so a quick solution, for above case may not be useful).

Update: My sets can overlap too. For exmaple I may have 4 sets like '0..7','1-6', '6-12',and '3..9'. I also need to eliminate duplicate items. In the case of overlapping sets, every resulting row should satisfy the initial condition. ie.. if I ask for 2 from '0..7' and 3 from '6..12', I should NOT have "6,7,6,7,8" or even "4,5,6,7,8" ( Here 0..7 has 4 entries rather than 2, as asked for.)

Replies are listed 'Best First'.
Re: Multiple Combinatorics
by davido (Archbishop) on Apr 15, 2012 at 08:46 UTC

I hope I'm understanding the problem. Regardless, it was an interesting puzzle (and a great question).

I understand you to be saying that you want all possible merges of the combinations of set A with the combinations of set B. And I think that by mentioning the potential for overlap you're saying that any merge that would result in an overlap should be disqualified. I don't think it is sufficient to just generate all combinations of set A, and all combinations of set B, merge, and shuffle. That would not result in a result set of all possible non-overlapping merges.

My solution creates all possible combinations of set A (3 at a time), and then iterates over that list in an outer loop while merging all possible combinations of set B (taken 2 at a time) that don't result in an overlap of any elements. The implementation sacrifices speed efficiency in favor of memory efficiency; we never actually hold onto any full list of combinations of either set A or set B. We just use the iterators for each set. That means that the set B combination object will be created and destroyed on every iteration from set A.

Here's the code:

```use strict;
use warnings;

use Math::Combinatorics;
use List::Util qw( first );

my @A = 'A' .. 'Z';
my @B = 0 .. 9;

my \$count = 0;
my \$comb_A = Math::Combinatorics->new( count => 3, data => [@A] );
while( my @combo_A = \$comb_A->next_combination ) {
my \$comb_B = Math::Combinatorics->new( count => 2, data => [@B] );
INNER: while( my @combo_B = \$comb_B->next_combination ) {
my %overlap;
@overlap{@combo_B} = ();
next INNER if first{ exists \$overlap{\$_} } @combo_A;
print join( ' ', @combo_A, @combo_B ), "\n";
\$count++;
}
}
print "Whew, that resulted in \$count result-producing iterations!\n";

The run:

```A B C 0 1
A B C 0 2
A B C 0 3
A B C 0 4
A B C 0 5
A B C 0 6
...
...
...
X Y Z 7 6
X Y Z 4 0
X Y Z 4 3
X Y Z 4 6
X Y Z 0 3
X Y Z 0 6
X Y Z 3 6
Whew, that resulted in 117000 result-producing iterations!

If overlaps between set A and set B are permitted, delete these three lines:

```        my %overlap;
@overlap{@combo_B} = ();
next INNER if first{ exists \$overlap{\$_} } @combo_A;

...and if I totally missed the boat I'm anxious to see a correct interpretation and the resulting solution code! :)

Update: In the update to your original post, and further clarified in a message to me, I see that you may be needing to merge multiple sets of combinations (ie, more than just two). I don't see any solution that doesn't involve diving deeper and deeper into nested loops, driving your big-O complexity higher with each additional set of combinations.

Is this just one way of approaching a more general problem that you're trying to solve?

Dave

Re: Multiple Combinatorics
by BrowserUk (Pope) on Apr 15, 2012 at 09:45 UTC

```#! perl -slw
use strict;

sub nFor(&@) {
my \$code = shift;
die "First argument must be a code ref" unless ref( \$code ) eq 'CO
+DE';
my @limits = @_;

my @indices = ( 0 ) x @limits;

for( my \$i = \$#limits; \$i >= 0; ) {
\$i = \$#limits;
\$code->( @indices ), ++\$indices[ \$i ]
while \$indices[ \$i ] < \$limits[ \$i ];
\$i = \$#limits;
\$indices[ \$i ] = 0, ++\$indices[ --\$i ]
while \$i >= 0 and \$indices[ \$i ] == \$limits[ \$i ];
}
}

my @a = 'a'..'z';
my @a3;
nFor{
push @a3, [ @a[ @_ ] ];
} ( scalar @a ) x 3;

my @b = 0 .. 9;
my @b2;
nFor{
push @b2, [ @b[ @_ ] ];
} ( scalar @b ) x 2;

nFor {
print join '-', @{ \$a3[ \$_[0] ] }, @{ \$b2[ \$_[1] ] };
} scalar @a3, scalar @b2;

__END__
C:\test>965138 | wc -l
1757600

C:\test>965138
a-a-a-0-0
a-a-a-0-1
a-a-a-0-2
a-a-a-0-3
...
z-z-z-9-6
z-z-z-9-7
z-z-z-9-8
z-z-z-9-9

With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

The start of some sanity?

Re: Multiple Combinatorics
by BrowserUk (Pope) on Apr 15, 2012 at 11:14 UTC
Update: My sets can overlap too. For exmaple I may have 4 sets like '0..7','1-6', '6-12',and '3..9'.

You don't say how many from each of those four sets, so I did 2 from each, but it should be obvious where to change the numbers supplied to permuteSets for different requirements.

The following runs in 1.7MB and takes a few seconds for wc -l to count the results:

```#! perl -slw
use strict;

sub nFor(&@) {
my \$code = shift;
die "First argument must be a code ref" unless ref( \$code ) eq 'CO
+DE';
my @limits = @_;

my @indices = ( 0 ) x @limits;

for( my \$i = \$#limits; \$i >= 0; ) {
\$i = \$#limits;
\$code->( @indices ), ++\$indices[ \$i ]
while \$indices[ \$i ] < \$limits[ \$i ];
\$i = \$#limits;
\$indices[ \$i ] = 0, ++\$indices[ --\$i ]
while \$i >= 0 and \$indices[ \$i ] == \$limits[ \$i ];
}
}

sub permuteSets (&@) {
my \$code = shift;
my @subsets;
while( @_ ) {
my( \$n, \$set ) = ( shift, shift );
push @subsets, [];
nFor { push @{ \$subsets[-1] }, [ @{ \$set }[ @_ ] ] } (scalar @
+{ \$set } ) x \$n;
}
nFor{
\$code->( map{ @{ \$subsets[ \$_ ][ \$_[ \$_ ] ] } } 0 .. \$#subsets
+ );
} map scalar @\$_, @subsets;
}

permuteSets{
print join '-', @_;
} 2, [0..7], 2, [1..6], 2, [6..12], 2, [3..9];

__END__
C:\test>965138 | wc -l
5531908

C:\test>965138
0-0-1-1-6-6-3-3
0-0-1-1-6-6-3-4
0-0-1-1-6-6-3-5
0-0-1-1-6-6-3-6
0-0-1-1-6-6-3-7
0-0-1-1-6-6-3-8
0-0-1-1-6-6-3-9
0-0-1-1-6-6-4-3
0-0-1-1-6-6-4-4
0-0-1-1-6-6-4-5
0-0-1-1-6-6-4-6
0-0-1-1-6-6-4-7
0-0-1-1-6-6-4-8
0-0-1-1-6-6-4-9
0-0-1-1-6-6-5-3
...
7-7-6-6-12-12-9-6
7-7-6-6-12-12-9-7
7-7-6-6-12-12-9-8
7-7-6-6-12-12-9-9
I also need to eliminate duplicate items. In the case of overlapping sets, every resulting row should satisfy the initial condition.

I don't suppose you'd care to describe the application?

AFAIK, there in no combinatorics algorithm that would address those requirements, so it is going to be a post-production step and is left as a exercise.

With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

The start of some sanity?

Re: Multiple Combinatorics
by mrguy123 (Hermit) on Apr 15, 2012 at 07:53 UTC
It's technically not possible, with things I like to do. Each set has specific # of picks and sets can overlap each other.
Re: Multiple Combinatorics
by JavaFan (Canon) on Apr 15, 2012 at 15:06 UTC
My sets can overlap too. For exmaple I may have 4 sets like '0..7','1-6', '6-12',and '3..9'. I also need to eliminate duplicate items. In the case of overlapping sets, every resulting row should satisfy the initial condition. ie.. if I ask for 2 from '0..7' and 3 from '6..12', I should NOT have "6,7,6,7,8" or even "4,5,6,7,8" ( Here 0..7 has 4 entries rather than 2, as asked for.)
With those conditions, nothing will satisfy if you'd have to pick at least one from '1..6'. Because any pick from that set will be a possible pick from the set '0..7', making that the count of picks from that set will be off.
If '0-7' has less number of picks compared to '1-6', then we will have no solution at all. But, it is possible that ('0-7','1-6') has (3,2) picks or even (2,2) picks respectively. In the first case there will be exactly 1 selection from 0 and 7 in any solution. In the 2nd case, 0 and 7 will not be selected in any solution.

Care to clarify that? Maybe add some examples? Cos like JavaFan, I don't get it.

As soon as you pick any two values from group 2 (1..6), you've also already picked two values from group 1 (0..7), so there is no way to comply with your "every resulting row should satisfy the initial condition" requirement.

You would either a) a pick two from both groups and therefore have 4 from the larger group; or b) pick nothing from the larger group, in which case you have nothing from the larger group.

With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

The start of some sanity?

Re: Multiple Combinatorics
by tobyink (Abbot) on Apr 15, 2012 at 21:05 UTC

My List::MapMulti module may help.

An example:

```use 5.010;
use List::MapMulti qw/iterator_multi/;

my @letters = 'a' .. 'z';
my @numbers = 0 .. 9;

my \$iter = iterator_multi \@letters, \@letters, \@letters, \@numbers,
+\@numbers;

# This loops through every possible combination
# of those lists.
#
while ( my @row = \$iter->() ) {
say join q(-), @row;
}
perl -E'sub Monkey::do{say\$_,for@_,do{(\$monkey=[caller(0)]->[3])=~s{::}{ }and\$monkey}}"Monkey say"->Monkey::do'
Re: Multiple Combinatorics
by brx (Pilgrim) on Apr 15, 2012 at 17:38 UTC
```my @set1=(0..7);
my @set2=(6..12);
my @comb;
{
local \$"=",";
@comb = glob"{@set1}-{@set1}-{@set2}-{@set2}-{@set2}"'
}
print join "\n",@comb;

Then you can grep {...} the result to exclude some results. Of course, this is not very good with big sets or a lot of combinations (time and memory).

Update: I probably post the worst solution beacause you don't want to duplicate elements from set1 and set2. It's possible to "grep" the result to avoid duplicate but it is not beautiful/effective... Anyway, this usage of glob must be known, so I don't delete my contribution :)

Create A New User
Node Status?
node history
Node Type: perlquestion [id://965136]
Approved by davido
Front-paged by Corion
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (3)
As of 2018-05-23 01:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
World peace can best be achieved by:

Results (166 votes). Check out past polls.

Notices?