Cartesian products, part 1
- April 27th, 2011
- Write comment
I was recently tasked with finding a solution to a particularly wonky problem that challenged me algorithmically, and I figured I'd write a little post on the experience. The fact that as software developers we are surrounded by a cornucopia of libraries, frameworks, gems, jars, etc. - while certainly beneficial to our execution of our craft - means that the opportunity to solve a really *interesting* problem comes up relatively rarely. So when I was given the job of finding all possible combinations of 1 element each from
distinct sets of varying length and contents (i.e the Cartesian product of
sets), I leapt at the opportunity to flex my long-underutilized comp-sci muscles. The problem and my own solution are given in detail below, with a few caveats: 1) If you don't like math, well, you should.
2) I wholeheartedly reserve the right to be wrong, and if you know a better way to acquire the same result, please email me!
So, here's the problem:
Given
distinct sets
, find all possible sets consisting of
elements, one from each set. Such a result is known as the Cartesian product, and can be represented mathematically as:

where
is the actual product. What does this product look like? Let's see an example.
We'll say we have two sets
and
:


The Cartesian product of these two sets would be a "set of sets", or more formally a collection or family of sets, and would look like the following:

As you can see if you examine the above, for each element of set
, there are exactly two sets in the result that contain that element - each of which also contains one or the other element of
. We have our product. Now let's figure out how we got here - and we'll start with the easy-to-compute question: How many possible combinations can there be?
Well, this is likely the easiest problem in combinatorics there is to solve. Remember above where we stated the formula for arriving at a Cartesian product?

Well, the number we're particularly after is the cardinality of the final collection
. In this context, the word "cardinality" means "size" - i.e. the cardinality of any set is the number of elements in that set. In mathematical notation, we represent the cardinality of a set by surrounding the name of the set by two vertical bars - very similar to what you may know as the notation for absolute value. So, the cardinality of the set
above would be represented as
and would be equal to 2, because set
has exactly 2 elements,
and
. So, given what we now know about cardinality, how do we use that to compute the cardinality of
, or
? It literally couldn't be easier:

That's right - you just multiply the cardinalities of all the sets in question together, and voilá: you have how many combinations there would be. Now, we've already stated that the cardinalty
of set
is equal to two, and it should be plain by now that the cardinality
of set
is also equal to two. As such, that would give us the answer 4 for
, and lo and behold, the result set
shown above has exactly 4 elements!
Now to the harder bit - how do we figure out precisely what those elements are? Enter my good friend and soon-to-be yours (if he isn't already), recursion. Below I have implemented the algorithm I worked out - though I make no claims to it being a heretofore undiscovered one - as a pure function (i.e. a function "without side-effects", which does not alter any external state) in Ruby, my programming language of choice. Note how the method calls itself as part of its execution - that's recursion.
def cartesian_product(*enums)
return Set.new([Set.new]) if enums.empty?
combos = Set.new
first = enums.shift
subproducts = cartesian_product(*enums)
subproducts.each do |product|
first.each do |element|
combos.add(product.clone.add(element))
end
end
combos
end
So there's the meat of the matter - more explanation to come in part 2, coming soon!