What's a good, non-recursive algorithm to calculate a Cartesian product?
algorithm, cartesian-product, language-agnostic, rebol
Solution
How about something like this:
#!/usr/bin/perl
use strict;
use warnings;
my @list1 = qw(1 2);
my @list2 = qw(3 4);
my @list3 = qw(5 6);
# Calculate the Cartesian Product
my @cp = cart_prod(\@list1, \@list2, \@list3);
# Print the result
foreach my $elem (@cp) {
print join(' ', @$elem), "\n";
}
sub cart_prod {
my @sets = @_;
my @result;
my $result_elems = 1;
# Calculate the number of elements needed in the result
map { $result_elems *= scalar @$_ } @sets;
return undef if $result_elems == 0;
# Go through each set and add the appropriate element
# to each element of the result
my $scale_factor = $result_elems;
foreach my $set (@sets)
{
my $set_elems = scalar @$set; # Elements in this set
$scale_factor /= $set_elems;
foreach my $i (0 .. $result_elems - 1) {
# Calculate the set element to place in this position
# of the result set.
my $pos = $i / $scale_factor % $set_elems;
push @{$result[$i]}, $$set[ $pos ];
}
}
return @result;
}
Which produces the following output:
1 3 5
1 3 6
1 4 5
1 4 6
2 3 5
2 3 6
2 4 5
2 4 6
Problem
Note This is not a REBOL-specific question. You can answer it in any language. Background The REBOL language supports the creation of domain-specific languages known as "dialects" in REBOL parlance. I've created such a dialect for list comprehensions, which aren't natively supported in REBOL. A good cartesian product algorithm is needed for list comprehensions. The Problem I've used meta-programming to solve this, by dynamically creating and then executing a sequence of nested `foreach` statements. It works beautifully. However, because it's dynamic, the code is not very readable. REBOL doesn't do recursion well. It rapidly runs out of stack space and crashes. So a recursive solution is out of the question. In sum, I want to replace my meta-programming with a readable, non-recursive, "inline" algorithm, if possible. The solution can be in any language, as long as I can reproduce it in REBOL. (I can read just about any programming language: C#, C, C++, Perl, Oz, Haskell, Erlang, whatever.) I should stress that this algorithm needs to support an arbitrary number of sets to be "joined", since list comprehension can involve any number of sets.