[Date Prev][Date Next][Thread Prev][Thread Next] [Search] [Date Index] [Thread Index]

[FWP] Cartesian Cross-Products



I was working on a Programming in Perl quiz for a college course I'm
taking (for the credit) and being a teacher's assistant for (for the
money, and for the fun).  One question was:

  Write a subroutine that returns the Cartesian cross product of a set of
  sets, where each set is represented by a single array.  Thus, the sub
  should take an arbitrary number of arrays (i.e. refs to arrays) and
  return an arbitrary number of arrays.  You may assume that it will never
  be called in scalar context.  (Remember that the Cartesian cross product
  of n sets is the set of all possible n-tuples (ordered sets of size
  n) whose first element is from the first of the n sets, the second
  element is from the second of the n sets, etc.)

I thought "hmm, that's a bit of a poser."  After just thinking up some
approaches, I suddenly hit upon one at a group meeting for another one of
my classes, at 11:00 PM.  In 20 minutes, I'd perfected it:

### PERL CODE TO FOLLOW ###

=pod Explanation of algorithm used

Given a list of sets, say ([a,b], [c,d,e], [f,g]), we first determine how
many sets can be created.  Mathematically, this is determined as follows:

  For a list of sets, { a[1], a[2], ..., a[n] }, to determine how many sets
  can be created by choosing an element from a[1] as the first element of a
  set, an element of a[2] for the second element, and so on, picking an
  element of a[n] as the n-th element, we create a list { s[1], s[2], ...,
  s[n] }, where each element s[i] is the number of element in a[i].  We can
  pick any of the s[i] elements from a[i] for the specified element in the
  set to be created, so the number of sets to be created is

      n
    -----
     | |   s[p]  .
     | |
     p=1

  That is, the product of the sizes of all the sets.

Now that we know how many sets we'll be creating, we start to populate these
sets.  We modify the same index of each set per loop; that is, we modify
a[0][0], a[1][0], a[2][0], ..., a[n][0], before we modify any index in a[1].

I utilize a "repetition value", which starts at 1, and is multiplied by the
size of the previous set (s[i-1]) when the population of a specific index of
the new sets is complete.  The repetition value indicates how many times the
specific element will be inserted in a row on a pass over an index.  The
starting value of 1 means that each element in a[0] will be inserted once, and
then the next element will be entered, and after all elements have been
exhausted, we go back to inserting a[0].

After we've exhausted a[0], we multiply the repetition value by s[0], and we
move on to a[1].  For each value here, we fill in the next index in the new
sets, but we do this R times in succession, where R is the repetition value.

We continue through until the new sets are completed.

=cut

  sub cartesian {
    my $len = 1;
    my (@ret,$rep,$i,$j,$p,$k);

    for (@_) { $len *= @$_ }

    for ($rep = 1, $i = 0; $i < @_; $rep *= @{ $_[$i] }, $i++) {
      for ($j = 0, $p = 0; $j < $len; $j += $rep, $p++) {
        for ($k = 0; $k < $rep; $k++) {
          print STDERR << "DEBUGGING" if 0;  # set to true to see debug output
repetition value: $rep
modifying set[@{[ $j + $k]}], index[$i]
value is element @{[ $p % @{ $_[$i] } ]} ('$_[$i][$p % @{ $_[$i] }]') of original set[$i]

DEBUGGING
          $ret[$j + $k][$i] = $_[$i][$p % @{ $_[$i] }]
        }
      }
    }

    return @ret;
  }

  # uncomment to see a test run
  # print map "@$_\n", cartesian( [1,2] , [3,4,5] , [6,7] );

__END__

-- 
MIDN 4/C PINYAN, NROTCURPI, US Naval Reserve             japhy@pobox.com
http://www.pobox.com/~japhy/                  http://pinyaj.stu.rpi.edu/
PerlMonth - An Online Perl Magazine            http://www.perlmonth.com/
The Perl Archive - Articles, Forums, etc.    http://www.perlarchive.com/


==== Want to unsubscribe from Fun With Perl?  Well, if you insist...
==== Send email to <fwp-request@technofile.org> with message _body_
====   unsubscribe