【问题标题】:How can I create combinations of several lists without hardcoding loops?如何在没有硬编码循环的情况下创建多个列表的组合?
【发布时间】:2023-03-03 03:40:01
【问题描述】:

我的数据如下所示:

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

我想做的是

  1. 生成part1partK 中的所有元素组合
  2. 找到@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";

        }
    }
}

【问题讨论】:

  • 嗯,没有什么比早上 DNA 编码的味道更香了。 :)

标签: perl algorithm nested-loops


【解决方案1】:

我会推荐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][$_], $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";
}

【讨论】:

  • 使用combinations() 意味着你创建了所有的元组。您可以使用while( my $tuple = $iterator->next ) 来避免将所有这些都放在内存中。
  • @FM & Brian:你的新修复给出了错误的结果。我得到了无限循环,每行都有“T-T-C 1 x 0.72 x 1 = 0.720”。
  • @foolishbrat 抱歉,再次修复。应该在编辑之前运行代码。我们需要的方法是get而不是next
  • 哦,那是我的错。对不起。 next() 向前看,但没有得到下一个元组。
【解决方案2】:

使用Algorithm::Loops 而不更改输入数据的解决方案如下所示:

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

但我认为您实际上可以通过将结构更改为类似的结构来使代码更清晰

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

因为没有并行数据结构,您可以简单地迭代可用的基本序列,而不是迭代索引,然后在两个不同的地方查找这些索引。

【讨论】:

  • @hobbs:您的方法还创建了不必要的成对和单一组合,(例如 T-T,T-TT, T--) 。有什么办法可以修改吗?
  • NestedLoops 接受一个可选的过滤器子程序,让您控制您的代码将被调用的组合。但默认情况下,它应该和问题中的原始代码做同样的事情,所以我不确定它应该做什么。
【解决方案3】:

为什么不使用递归?将深度作为参数传递,让函数在循环内以 depth+1 调用自身。

【讨论】:

  • 为什么在不需要的时候使用递归? Perl 没有尾递归,所以它在其他语言中运行的主要原因通常会在 Perl 中杀死你。
  • 递归到 50 的深度在任何语言中都是完全可以接受的。不要不必要地使代码复杂化以避免递归。
  • 使您的代码复杂化?使用递归更复杂。 :) 而且,你不知道它只会有 50 深。当人们在新情况下使用它们时,程序往往会扩大其极限。既然规避风险这么容易,为什么还要冒险呢? :)
【解决方案4】:

您可以通过创建与@homopol 数组(例如N)长度相同的索引数组来做到这一点,以跟踪您正在查看的组合。实际上这个数组就像一个 以 N 为底的数字,元素为数字。以与在基数 N 中写下连续数字相同的方式进行迭代,例如 (0 0 0 ... 0), (0 0 0 ... 1), ...,(0 0 0 ... N- 1), (0 0 0 ... 1 0), ....

【讨论】:

    【解决方案5】:

    方法 1:从索引计算

    计算homopol中长度的乘积(length1 * length2 * ... * lengthN)。然后,从零迭代 i 到产品。现在,您想要的索引是 i % length1, (i / length1)%length2, (i / length1 / length2) % length3, ...

    方法 2:递归

    我被打败了,请参阅 nikie 的回答。 :-)

    【讨论】:

      猜你喜欢
      • 2010-12-14
      • 1970-01-01
      • 2017-10-25
      • 2019-12-06
      • 2020-06-21
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多