views:

1732

answers:

6

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.

+4  A: 

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
Robert Gamble
That looks great! I'm going to wait and see if I get any other responses (for curiosity's sake) before I award the answer, but I think you've got it.
Gregory Higley
If you're doing this in Perl, just use Set::CrossProduct
brian d foy
A: 

EDIT: This solution doesn't work. Robert Gamble's is the correct solution.

I brainstormed a bit and came up with this solution:

(I know most of you won't know REBOL, but it's a fairly readable language.)

REBOL []

sets: [[1 2 3] [4 5] [6]] ; Here's a set of sets
elems: 1
result: copy []
foreach set sets [elems: elems * (length? set)]
for n 1 elems 1 [
    row: copy []
    foreach set sets [
        index: 1 + (mod (n - 1) length? set)
        append row set/(index)
    ]
    append/only result row
]

foreach row result [
    print result
]

This code produces:

1 4 6
2 5 6
3 4 6
1 5 6
2 4 6
3 5 6

(Upon first reading the numbers above, you may think there are duplicates. I did. But there aren't.)

Interestingly, this code uses almost the very same algorithm (1 + ((n - 1) % 9) that torpedoed my Digital Root question.

Gregory Higley
Unfortunately, this algorithm has the problem that it ONLY works on sets with different lengths. Back to the drawing board!
Gregory Higley
A: 

For the sake of completeness, Here's Robert Gamble's answer translated into REBOL:

REBOL []

cartesian: func [
    {Given a block of sets, returns the Cartesian product of said sets.}
    sets [block!] {A block containing one or more series! values}
    /local
     elems
     result
     row
][
    result: copy []

    elems: 1
    foreach set sets [
     elems: elems * (length? set)
    ]

    for n 0 (elems - 1) 1 [
     row: copy []
     skip: elems
     foreach set sets [
      skip: skip / length? set
      index: (mod to-integer (n / skip) length? set) + 1 ; REBOL is 1-based, not 0-based
      append row set/(index)
     ]
     append/only result row
    ]

    result
]

foreach set cartesian [[1 2] [3 4] [5 6]] [
    print set
]

; This returns the same thing Robert Gamble's solution did:

1 3 5
1 3 6
1 4 5
1 4 6
2 3 5
2 3 6
2 4 5
2 4 6
Gregory Higley
+2  A: 

3 times Faster and less memory used (less recycles).

cartesian: func [
 d [block! ] 
 /local len set i res

][
 d: copy d
 len: 1
 res: make block! foreach d d [len: len * length? d]
 len: length? d
 until [
  set: clear []
  loop i: len [insert set d/:i/1   i: i - 1]
  res: change/only res copy set
  loop i: len [
   unless tail? d/:i: next d/:i [break]
   if i = 1 [break]
   d/:i: head d/:i
   i: i - 1
  ]
  tail? d/1
 ]
 head res
]
Steeve
Excellent! You are obviously twice the reboller I am.
Gregory Higley
A: 
use strict;
use Data::Dumper;

print Dumper getCartesian(
  [qw(1 2)],
  [qw(3 4)],
  [qw(5 6)],
);

sub getCartesian {
#
  my @input = @_;
  my @ret = @{ shift @input };

  for my $a2 (@input) {
    @ret = map {
      my $v = (ref) ? $_ : [$_];
      map [@$v, $_], @$a2;
    }
    @ret;
  }
  return @ret;
}
mpapec
A: 

Here is a Java code to generate Cartesian product for arbitrary number of sets with arbitrary number of elements.

in this sample the list "ls" contains 4 sets (ls1,ls2,ls3 and ls4) as you can see "ls" can contain any number of sets with any number of elements.

import java.util.*;

public class CartesianProduct {

private List> ls = new ArrayList>();
private List ls1 = new ArrayList();
private List ls2 = new ArrayList();
private List ls3 = new ArrayList();
private List ls4 = new ArrayList();

public List generateCartesianProduct(){

List set1 = null;
List set2 = null;


ls1.add("a");
ls1.add("b");
ls1.add("c");

ls2.add("a2");
ls2.add("b2");
ls2.add("c2");

ls3.add("a3");
ls3.add("b3");
ls3.add("c3");
ls3.add("d3");

ls4.add("a4");
ls4.add("b4");

ls.add(ls1);
ls.add(ls2);
ls.add(ls3);
ls.add(ls4);

boolean subsetAvailabe = true;
int setCount = 0;


try{    
    set1 = augmentSet(ls.get(setCount++),ls.get(setCount));
   }catch(IndexOutOfBoundsException ex){
      if(set1 == null){
          set1 = ls.get(0);
      }
        return set1;
   }

 do{    
    try{
         setCount++;         
         set1 = augmentSet(set1,ls.get(setCount));

     }catch(IndexOutOfBoundsException ex){
         subsetAvailabe = false;
     }

   }while(subsetAvailabe);  

   return set1;

}




public List augmentSet(List set1, List set2){

     List augmentedSet = new ArrayList(set1.size() * set2.size());

        for(String elem1 : set1){
            for(String elem2 : set2){
                augmentedSet.add(elem1 + "," + elem2);
            }
        }

        set1 = null; set2 = null;

        return augmentedSet;
}

public static void main(String[] arg){
 CartesianProduct cp = new CartesianProduct();
 List cartesionProduct = cp.generateCartesianProduct();
for(String val : cartesionProduct){
    System.out.println(val);
}

}

}
Thomas Samimuthu