4

Let's say I have a class of 30 students and want generate every possible way in which they can be partitioned into groups of 5 (order is irrelevant).

I know how to find all the combinations of students to form one group individually (http://www.merriampark.com/comb.htm). By using that iterator and some recursion, I can find PERMUTATIONS of the possible group combinations. However, order in which the groups are selected isn't relevant and I'd like to minimize my execution time. So how do I find the unique COMBINATIONS of the possible groups?

The above algorithm uses lexicographical ordering to avoid generating duplicate combinations... is there a way that I can use that idea on groups instead of on objects?

I know Ruby well and Java/Python less well. Thanks in advance for any advice!

  • Perhaps take a look here, particularly the `multiset` functions. It's Perl, but it should give you some code to poke at: http://search.cpan.org/perldoc/Math::Combinatorics – Telemachus Oct 26 '09 at 01:36
  • Thanks... knowing that it's a "multiset" will make my Googling better as well. – Jordan Smith Oct 26 '09 at 01:40

4 Answers4

5

Well, there's (30C5*25C5*20C5*15C5*10C5*5C5)/6! = 30!/(6!*5!6) = 123,378,675,083,039,376 different partitons of 30 into groups of 5, so generating them all will take some time, no matter what method you use.

In general, though, a good method to selecting such a partition is to use some ordering on the elements, and find the grouping for the highest ungrouped element, and then group the rest.

     find_partition = lambda do |elts|
        if elts.empty?
         [[]]
        else
         highest = elts.pop
         elts.combination(4).map do |others|
            find_partition[elts - others].map { |part| part << [highest,*others] }
         end.inject(:+)
        end
     end
     find_partition[(1..30).to_a]

This way you're only generating each partition once

rampion
  • 87,131
  • 49
  • 199
  • 315
  • Could you please elaborate on why it's not just 30c5? I haven't looked at combinatorial math since 2001! – Josh Oct 26 '09 at 05:10
  • 1
    Well, there's 30c5 ways of choosing the first group of 5, 25c5 of choosing the second,..., 10c5 ways of choosing the fifth group of 5, and 5c5=1 ways of choosing the last. However, since we're doing a partition, we don't care about the order that we get these groups in. Since there's 6! ways to order 6 groups, we divide by 6!. This gives the extended product in the post. – rampion Oct 26 '09 at 12:53
  • Thanks rampion... using this math, I was able to convince my project leader that we needed to take another approach to improve scalability. – Jordan Smith Oct 28 '09 at 23:43
1

This is an old question, but anyway, for the record, that's how I would it in Ruby:

class Array
  def groups_of_size(n)
    Enumerator.new do |yielder|
      if self.empty?
        yielder.yield([])
      else
        self.drop(1).combination(n-1).map { |vs| [self.first] + vs }.each do |values|
          (self - values).groups_of_size(n).each do |group|
            yielder.yield([values] + group)
          end   
        end
      end
    end
  end
end

I use an enumerator because the output can grow very quickly, a strict output (an array for example) wouldn't be useful. A usage example:

>> pp [0, 1, 2, 3, 4, 5].groups_of_size(3).to_a
=> 
[[[0, 1, 2], [3, 4, 5]],
 [[0, 1, 3], [2, 4, 5]],
 [[0, 1, 4], [2, 3, 5]],
 [[0, 1, 5], [2, 3, 4]],
 [[0, 2, 3], [1, 4, 5]],
 [[0, 2, 4], [1, 3, 5]],
 [[0, 2, 5], [1, 3, 4]],
 [[0, 3, 4], [1, 2, 5]],
 [[0, 3, 5], [1, 2, 4]],
 [[0, 4, 5], [1, 2, 3]]]
tokland
  • 66,169
  • 13
  • 144
  • 170
0

You could do some post-processing on the permutations. Some pseudo-code (implement in the language of your choice...):

// We have a list of lists called 'permutations'
// combinations is an (empty) list of lists
for each permutation in permutations
{
   sortedPermutation = permutation.sort()
   if (! combinations.find(sortedPermutation) )
   {
       combinations.add(sortedPermutation);
   }
}

Probably not the most efficient; I'd add the sort & compare to the code that generates the permutations personally.

Josh
  • 3,540
  • 1
  • 21
  • 12
0

One possibility would be to find all combinations to form an individual group, then go through and generate combinations that don't contain members of that individual group. Something like:

List<List<Student>> combinations=Combinations(students);
public void GenerateCombinations(int startingIndex, List<List<Student>> currentGroups, int groupsLeft)
{
    if(groupsLeft==0) ProcessCombination(currentGroups);

    for(int i=startingIndex; i<combinations.Count; i++)
    {
        if combinations[i] does not contain a student in current groups
            GenerateCombinations(i+1, currentGroups + combinations[i], groupsLeft -1);
    }
}

It won't be the most efficient method to go about it, but it should generate all combinations of groups. I suspect better performance could be had if you were to generate temporary lists of combinations, where in all groups that can't occur were removed, but that would be a bit more complex.

As a slight aside, there should be 142,506 combinations of 30 students to form a single group of 5. My <sarcasm> awesome </sarcasm> math skills suggest that there should be about 10^17 = 100 quadrillion combinations of groups of students (30!/((5!^6)*6!); 30! orderings of students, ordering of 6 groups of 5 does not matter, and ordering of those 6 groups doesn't matter). You might be sitting there a while waiting for this to finish.

CoderTao
  • 3,831
  • 1
  • 23
  • 18