Tutorial :How can I create combinations of several lists without hardcoding loops?



Question:

I have data that looks like this:

    my @homopol = (                     ["T","C","CC","G"],  # part1                     ["T","TT","C","G","A"], #part2                     ["C","CCC","G"], #part3 ...upto part K=~50                    );          my @prob = ([1.00,0.63,0.002,1.00,0.83],                  [0.72,0.03,1.00, 0.85,1.00],                  [1.00,0.97,0.02]);         # Note also that the dimension of @homopol is always exactly the same with @prob.     # Although number of elements can differ from 'part' to 'part'.  

What I want to do is to

  1. Generate all combinations of elements in part1 through out partK
  2. Find the product of the corresponding elements in @prob.

Hence at the end we hope to get this output:

T-T-C  1 x 0.72 x 1 = 0.720  T-T-CCC     1 x 0.72 x 0.97 = 0.698  T-T-G  1 x 0.72 x 0.02 = 0.014  ...  G-G-G  1 x 0.85 x 0.02 = 0.017  G-A-C  1 x 1 x 1 = 1.000  G-A-CCC     1 x 1 x 0.97 = 0.970  G-A-G  1 x 1 x 0.02 = 0.020  

The problem is that the following code of mine does that by hardcoding the loops. Since the number of parts of @homopol is can be varied and large (e.g. ~K=50), we need a flexible and compact way to get the same result. Is there any? I was thinking to use Algorithm::Loops, but not sure how to achieve that.

use strict;  use Data::Dumper;  use Carp;      my @homopol = (["T","C","CC","G"],                 ["T","TT","C","G","A"],                 ["C","CCC","G"]);      my @prob = ([1.00,0.63,0.002,1.00,0.83],              [0.72,0.03,1.00, 0.85,1.00],              [1.00,0.97,0.02]);        my $i_of_part1 = -1;  foreach my $base_part1 ( @{ $homopol[0] } ) {      $i_of_part1++;      my $probpart1 = $prob[0]->[$i_of_part1];        my $i_of_part2 =-1;      foreach my $base_part2 ( @{ $homopol[1] } ) {          $i_of_part2++;          my $probpart2 = $prob[1]->[$i_of_part2];            my $i_of_part3 = -1;          foreach my $base_part3 ( @{ $homopol[2] } ) {              $i_of_part3++;              my $probpart3 = $prob[2]->[$i_of_part3];                my $nstr = $base_part1."".$base_part2."".$base_part3;              my $prob_prod = sprintf("%.3f",$probpart1 * $probpart2 *$probpart3);                print "$base_part1-$base_part2-$base_part3 \t";              print "$probpart1 x $probpart2 x $probpart3 = $prob_prod\n";            }      }  }  


Solution:1

I would recommend Set::CrossProduct, which will create an iterator to yield the cross product of all of your sets. Because it uses an iterator, it does not need to generate every combination in advance; rather, it yields each one on demand.

use strict;  use warnings;  use Set::CrossProduct;    my @homopol = (      [qw(T C CC G)],      [qw(T TT C G A)],      [qw(C CCC G)],   );    my @prob = (      [1.00,0.63,0.002,1.00],      [0.72,0.03,1.00, 0.85,1.00],      [1.00,0.97,0.02],  );    # Prepare by storing the data in a list of lists of pairs.  my @combined;  for my $i (0 .. $#homopol){      push @combined, [];      push @{$combined[-1]}, [$homopol[$i][$_], $prob[$i][$_]]          for 0 .. @{$homopol[$i]} - 1;  };    my $iterator = Set::CrossProduct->new([ @combined ]);  while( my $tuple = $iterator->get ){      my @h = map { $_->[0] } @$tuple;      my @p = map { $_->[1] } @$tuple;      my $product = 1;      $product *= $_ for @p;      print join('-', @h), ' ', join(' x ', @p), ' = ', $product, "\n";  }  


Solution:2

A solution using Algorithm::Loops without changing the input data would look something like:

use Algorithm::Loops;    # Turns ([a, b, c], [d, e], ...) into ([0, 1, 2], [0, 1], ...)  my @lists_of_indices = map { [ 0 .. @$_ ] } @homopol;    NestedLoops( [ @lists_of_indices ], sub {    my @indices = @_;    my $prob_prod = 1; # Multiplicative identity    my @base_string;    my @prob_string;    for my $n (0 .. $#indices) {      push @base_string, $hompol[$n][ $indices[$n] ];      push @prob_string, sprintf("%.3f", $prob[$n][ $indices[$n] ]);      $prob_prod *= $prob[$n][ $indices[$n] ];    }    print join "-", @base_string; print "\t";    print join "x", @prob_string; print " = ";    printf "%.3f\n", $prob_prod;  });  

But I think that you could actually make the code clearer by changing the structure to one more like

[     { T => 1.00, C => 0.63, CC => 0.002, G => 0.83 },    { T => 0.72, TT => 0.03, ... },    ...  ]  

because without the parallel data structures you can simply iterate over the available base sequences, instead of iterating over indices and then looking up those indices in two different places.


Solution:3

Why don't you use recursion? Pass the depth as a parameter and let the function call itself with depth+1 inside the loop.


Solution:4

you could do it by creating an array of indicies the same length as the @homopol array (N say), to keep track of which combination you are looking at. In fact this array is just like a number in base N, with the elements being the digits. Iterate in the same way as you would write down consectutive numbers in base N, e.g (0 0 0 ... 0), (0 0 0 ... 1), ...,(0 0 0 ... N-1), (0 0 0 ... 1 0), ....


Solution:5

Approach 1: Calculation from indices

Compute the product of lengths in homopol (length1 * length2 * ... * lengthN). Then, iterate i from zero to the product. Now, the indices you want are i % length1, (i / length1)%length2, (i / length1 / length2) % length3, ...

Approach 2: Recursion

I got beaten to it, see nikie's answer. :-)


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »