¿Cómo puedo crear combinaciones de varias listas sin bucles de codificación?
-
22-07-2019 - |
Pregunta
Tengo datos que se ven así:
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'.
Lo que quiero hacer es
- Genere todas las combinaciones de elementos en
part1
a través departK
- Encuentre el producto de los elementos correspondientes en
@prob
.
Por lo tanto, al final esperamos obtener este resultado:
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
El problema es que el siguiente código mío lo hace codificando
los bucles Dado que el número de partes de @homopol
puede ser variado y grande
(por ejemplo, ~ K = 50), necesitamos una forma flexible y compacta para obtener el mismo resultado. ¿Hay alguna?
Estaba pensando en usar Algorithm :: Loops , pero no estoy seguro de cómo lograrlo.
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";
}
}
}
Solución
Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
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][ Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
<*>], $prob[$i][ Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
<*>]]
for 0 .. @{$homopol[$i]} - 1;
};
my $iterator = Set::CrossProduct->new([ @combined ]);
while( my $tuple = $iterator->get ){
my @h = map { Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
<*>->[0] } @$tuple;
my @p = map { Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
<*>->[1] } @$tuple;
my $product = 1;
$product *= Recomendaría Set :: CrossProduct
, que creará un iterador para obtener el producto cruzado de todos sus conjuntos. Debido a que utiliza un iterador, no necesita generar todas las combinaciones de antemano; más bien, rinde cada uno bajo demanda.
<*> for @p;
print join('-', @h), ' ', join(' x ', @p), ' = ', $product, "\n";
}
Otros consejos
Una solución que utiliza Algorithm :: Loops sin cambiar los datos de entrada se vería algo así :
use Algorithm::Loops;
# Turns ([a, b, c], [d, e], ...) into ([0, 1, 2], [0, 1], ...)
my @lists_of_indices = map { [ 0 .. @ Una solución que utiliza Algorithm :: Loops sin cambiar los datos de entrada se vería algo así :
[
{ T => 1.00, C => 0.63, CC => 0.002, G => 0.83 },
{ T => 0.72, TT => 0.03, ... },
...
]
Pero creo que realmente podría aclarar el código cambiando la estructura a una más como
<*>
porque sin las estructuras de datos paralelas simplemente puede iterar sobre las secuencias de base disponibles, en lugar de iterar sobre índices y luego buscar esos índices en dos lugares diferentes.
] } @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;
});
Pero creo que realmente podría aclarar el código cambiando la estructura a una más como
<*>porque sin las estructuras de datos paralelas simplemente puede iterar sobre las secuencias de base disponibles, en lugar de iterar sobre índices y luego buscar esos índices en dos lugares diferentes.
¿Por qué no usas la recursividad? Pase la profundidad como parámetro y deje que la función se llame a sí misma con profundidad + 1 dentro del bucle.
podría hacerlo creando una matriz de indicadores de la misma longitud que la matriz @homopol (N say), para realizar un seguimiento de qué combinación está viendo. De hecho, esta matriz es como un número en la base N, siendo los elementos los dígitos. Itere de la misma manera que escribiría números consecutivos en la base N, por ejemplo (0 0 0 ... 0), (0 0 0 ... 1), ..., (0 0 0 ... N- 1), (0 0 0 ... 1 0), ....
Enfoque 1: Cálculo a partir de índices
Calcule el producto de longitudes en homopol (length1 * length2 * ... * lengthN). Luego, repita i desde cero hasta el producto. Ahora, los índices que desea son i% length1, (i / length1)% length2, (i / length1 / length2)% length3, ...
Enfoque 2: Recursión
Me golpearon, ver la respuesta de Nikie. :-)