Как создать комбинации из нескольких списков без циклов жесткого кодирования?

StackOverflow https://stackoverflow.com/questions/1442965

  •  22-07-2019
  •  | 
  •  

Вопрос

У меня есть данные, которые выглядят так:

    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'.

Что я хочу сделать, так это

<Ол>
  • Создать все комбинации элементов в part1 через partK
  • Найдите произведение соответствующих элементов в @prob .
  • Следовательно, в конце мы надеемся получить этот вывод:

    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
    

    Проблема в том, что мой следующий код делает это путем жесткого кодирования петли. Поскольку количество частей @homopol может быть различным и большим (например, ~ K = 50), нам нужен гибкий и компактный способ получить тот же результат. Есть ли? Я думал использовать Algorithm :: Loops , но не уверен, как этого добиться.

    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";
    
            }
        }
    }
    
    Это было полезно?

    Решение

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    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][

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    <*>], $prob[$i][

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    <*>]] for 0 .. @{$homopol[$i]} - 1; }; my $iterator = Set::CrossProduct->new([ @combined ]); while( my $tuple = $iterator->get ){ my @h = map {

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    <*>->[0] } @$tuple; my @p = map {

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    <*>->[1] } @$tuple; my $product = 1; $product *=

    Я бы порекомендовал Set :: CrossProduct , который создаст итератор для получения перекрестного произведения всех ваших множеств. Поскольку он использует итератор, ему не нужно заранее генерировать каждую комбинацию; скорее он выдает каждый по требованию.

    <*> for @p; print join('-', @h), ' ', join(' x ', @p), ' = ', $product, "\n"; }

    Другие советы

    Решение, использующее Algorithm :: Loops без изменения входных данных, будет выглядеть примерно так

    use Algorithm::Loops;
    
    # Turns ([a, b, c], [d, e], ...) into ([0, 1, 2], [0, 1], ...)
    my @lists_of_indices = map { [ 0 .. @

    Решение, использующее Algorithm :: Loops без изменения входных данных, будет выглядеть примерно так

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

    Но я думаю, что вы могли бы на самом деле сделать код более понятным, изменив структуру на более похожую

    <*>

    потому что без параллельных структур данных вы можете просто выполнять итерации по доступным базовым последовательностям вместо того, чтобы выполнять итерации по индексам, а затем искать эти индексы в двух разных местах.

    ] } @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; });

    Но я думаю, что вы могли бы на самом деле сделать код более понятным, изменив структуру на более похожую

    <*>

    потому что без параллельных структур данных вы можете просто выполнять итерации по доступным базовым последовательностям вместо того, чтобы выполнять итерации по индексам, а затем искать эти индексы в двух разных местах.

    Почему вы не используете рекурсию? Передайте глубину как параметр и позвольте функции вызывать себя с глубиной + 1 внутри цикла.

    Вы можете сделать это, создав массив показателей той же длины, что и массив @homopol (N скажем), чтобы отслеживать, какую комбинацию вы просматриваете. На самом деле этот массив так же, как число в базе N, с элементами, являющимися цифрами. Итерируйте так же, как вы бы записали последовательные числа в базе N, например (0 0 0 ... 0), (0 0 0 ... 1), ..., (0 0 0 ... N- 1), (0 0 0 ... 1 0), ....

    Подход 1. Расчет по индексам

    Вычислить произведение длин в гомополе (length1 * length2 * ... * lengthN). Затем итерируйте i от нуля до произведения. Теперь вам нужны следующие индексы: i% length1, (i / length1)% length2, (i / length1 / length2)% length3, ...

    Подход 2: рекурсия

    Меня избили, смотрите ответ Ники. : -)

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top