【问题标题】:Wanted: a quicker way to check all combinations within a very large hash需要:一种更快的方法来检查一个非常大的散列中的所有组合
【发布时间】:2010-07-23 20:41:17
【问题描述】:

我有一个包含大约 130,000 个元素的散列,我正在尝试检查该散列中的所有组合(130,000 x 130,000 个组合)。我的代码如下所示:

 foreach $key1 (keys %CNV)
 {

  foreach $key2 (keys %CNV)
  {
         if (blablabla){do something that doesn't take as long}
  }

 }

如您所料,这需要很长时间才能运行。有谁知道更快的方法来做到这一点?非常感谢提前!

-阿卜杜勒

编辑:blablabla 的更新。

大家好,感谢所有反馈!真的很感激。我将 foreach 语句更改为:

for ($j=1;$j<=24;++$j)
 {
  foreach $key1 (keys %{$CNV{$j}})
  {

   foreach $key2 (keys %{$CNV{$j}})
   {
                        if (blablabla){do something}
                        }
                }
        }

哈希现在是多维的:

$CNV{chromosome}{$start,$end}

我会根据要求详细说明我到底想要做什么。

blablabla 如下:

if  ( (($CNVstart{$j}{$key1} >= $CNVstart{$j}{$key2}) && ($CNVstart{$j}{$key1} <= $CNVend{$j}{$key2})) ||
   (($CNVend{$j}{$key1} >= $CNVstart{$j}{$key2}) && ($CNVend{$j}{$key1} <= $CNVend{$j}{$key2})) ||
   (($CNVstart{$j}{$key2} >= $CNVstart{$j}{$key1}) && ($CNVstart{$j}{$key2} <= $CNVend{$j}{$key1})) ||
   (($CNVend{$j}{$key2} >= $CNVstart{$j}{$key1}) && ($CNVend{$j}{$key2} <= $CNVend{$j}{$key1})) 
  )    

简而言之:散列元素代表 DNA 的特定部分(所谓的“CNV”,现在将其视为基因),具有开始和结束(它们是表示它们在特定位置的整数)染色体,存储在具有相同键的哈希中:%CNVstart & %CNVend)。我正在尝试检查 CNV 的每个组合是否重叠。如果一个家庭中有两个重叠的元素(我的意思是一个我拥有并读入其 DNA 的人的家庭;在 foreach 语句中还有一个 for 语句,让程序为每个家庭检查这个,这使得它持续更长时间),我检查它们是否也具有相同的“副本号”(存储在具有相同键的另一个哈希中)并打印出结果。

感谢大家的宝贵时间!

【问题讨论】:

  • 您将不得不告诉我们您实际想要完成的任务,而不是您当前尝试完成的方式。您目前所问的内容等同于“我怎样才能更快地进行 16,900,000,000 次计算?”
  • 只要您不提供更多细节,我们只能推荐一般的加速技术。我想到的一些:a)从 perl 切换到 Java、C++、Assembler b)并行化任务 c)升级你的硬件
  • @Abdel,这很重要。正如彼得所说,要么告诉我们你在做什么,要么我们无能为力。
  • 您问错了问题:如何更快地检查 N*N 组合?您应该问的问题——以及一位测试员的评论隐含地提出——是是否有可能避免进行所有这些检查。我的第一个猜测是您的较大算法、数据结构或两者都有缺陷。我们需要更多信息来帮助解决这个问题。
  • -1 拒绝帮助那些试图为你寻找答案的人。可能可以避免这 16,900,000,000 次查找中的一些(或实际上大部分),但您并不清楚是否确实如此,或者您是否只是询问是否有更快的方法来索引散列。可能可以反转你的循环,或者添加一些短路,但你对那些试图找出答案的人来说是咄咄逼人的。

标签: perl hash


【解决方案1】:

听起来Algorithm::Combinatorics 可以在这里为您提供帮助。它旨在提供“组合序列的有效生成”。来自其文档:

算法::组合学是一种 高效组合生成器 序列。 ...迭代器不使用 递归,也不是堆栈,并且被写入 在 C 中。

您可以使用其combinations 子例程从您的全套按键中提供所有可能的 2 按键组合。

另一方面,Perl 本身是用 C 编写的。所以老实说,我不知道这是否会有所帮助。

【讨论】:

    【解决方案2】:

    也许通过使用并发?但是你必须小心你对正匹配所做的事情,以免出现问题。

    例如取 $key1,将其拆分为 $key1A 和 §key1B。创建两个单独的线程,每个线程都包含“一半的循环”。

    我不确定在 Perl 中启动新线程到底有多昂贵,但如果您的积极操作不必同步,我想在匹配的硬件上您会更快。

    值得一试恕我直言。

    【讨论】:

    • 这是个好主意!谢谢!因为哈希实际上存在多个键: CNV{$key1,$key2,$key3} 但我不知道如何将我的 foreach 语句中的键分成三个键。如果你能帮助我,我的问题解决了!
    • 所以您使用的是多维数组仿真而不是真正的多维支持?也许从那里开始,使用嵌套哈希 ($CNV{$key1}L$key2}{$key}) 而不是由 \034 连接的键 ($CNV{$key1,$key2,$key3},阅读 perldoc perldsc 了解更多信息.
    • 查看 Parallel::ForkManager 以将任务拆分为单独的进程(我不推荐线程)和某种形式的 IPC(查看 perldoc perlipc 以获取结果)来管理获取结果。一旦你有适当的多维哈希,你应该能够根据范围将你的 foreach 拆分到叉子上。
    【解决方案3】:

    定义废话。

    你可以这样写:

    foreach $key1 (keys %CNV) {

    if (blah1)
    {
        foreach $key2 (keys %CNV)
        {
            if (blah2){do something that doesn't take as long}
        }
    }
    

    }

    这个pass应该是O(2N)而不是O(N^2)

    【讨论】:

    • blabla 现在在开头的帖子中定义。
    • -1 表示 O(2N)。如果 N 和 blah1 为真的概率之间没有依赖关系,它仍然是 O(N^2)。顺便说一句,O(2N) = O(N)。
    【解决方案4】:

    问题中的数据结构不太适合问题。让我们这样试试吧。

    use Set::IntSpan::Fast::XS;
    my @CNV;
    for ([3, 7], [4, 8], [9, 11]) {
        my $set = Set::IntSpan::Fast::XS->new;
        $set->add_range(@{$_});
        push @CNV, $set;
    }
    
    # The comparison is commutative, so we can cut the total number in half.
    for my $index1 (0 .. -1+@CNV) {
        for my $index2 (0 .. $index1) {
            next if $index1 == $index2; # skip if it's the same CNV
            say sprintf(
                'overlap of CNV %s, %s at indices %d, %d',
                $CNV[$index1]->as_string, $CNV[$index2]->as_string, $index1, $index2
            ) unless $CNV[$index1]->intersection($CNV[$index2])->is_empty;
        }
    }
    

    输出:

    overlap of CNV 4-8, 3-7 at indices 1, 0
    

    我们不会得到3-7, 4-8 的重叠,因为它是重复的。

    还有Bio::Range,但在我看来效率不高。您绝对应该与bio.perl.org/open-bio 人取得联系;很有可能你所做的事情已经被做了一百万次,然后他们才算出最佳算法。

    【讨论】:

    • 感谢您的回答!看起来你的方式让我跳过了相互比较相同的 CNV,这为我节省了 130,000 次比较(相对不是很多,但我将它合并到我的脚本中)。它还让我可以跳过两次比较相同的组合(这将节省一半的比较)。但是这样做真的可以节省时间吗,因为 for 语句仍然必须经过所有比较才能跳过“下一个”和“除非”语句?
    • 我很确定这个 XS 的东西比多维散列更快,但你可以用你的真实数据benchmark / profile 它。
    【解决方案5】:

    我想我找到了答案 :-) 不过没有你们是做不到的。我找到了一种方法来跳过我所做的大部分比较:

    for ($j=1;$j<=24;++$j)
     {
                foreach $key1 (sort keys %{$CNV{$j}})
                {
    
    
                    foreach $key2 (sort keys %{$CNV{$j}})
                    {
    
                        if (($CNVstart{$j}{$key2} < $CNVstart{$j}{$key1}) && ($CNVend{$j}{$key2} < $CNVstart{$j}{$key1}))
                        {
                        next;
                        }
    
    
                        if (($CNVstart{$j}{$key2} > $CNVend{$j}{$key1}) && ($CNVend{$j}{$key2} > $CNVend{$j}{$key1}))
                        {
                        last;
                        }
    
            if  ( (($CNVstart{$j}{$key1} >= $CNVstart{$j}{$key2}) && ($CNVstart{$j}{$key1} <= $CNVend{$j}{$key2})) ||
               (($CNVend{$j}{$key1} >= $CNVstart{$j}{$key2}) && ($CNVend{$j}{$key1} <= $CNVend{$j}{$key2})) ||
               (($CNVstart{$j}{$key2} >= $CNVstart{$j}{$key1}) && ($CNVstart{$j}{$key2} <= $CNVend{$j}{$key1})) ||
               (($CNVend{$j}{$key2} >= $CNVstart{$j}{$key1}) && ($CNVend{$j}{$key2} <= $CNVend{$j}{$key1})) 
              )    {print some stuff out}
    
        }
        }
    }
    

    我所做的是:

    • 对每个 foreach 循环的哈希键进行排序
    • 如果带有 $key2 的 CNV 仍未到达带有 $key1 的 CNV(即 start2 和 end2 都小于 start1),则执行“下一步”
    • 并且可能是最节省时间的方法:如果带有 $key2 的 CNV 已经超过带有 $key1 的 CNV(即 start2 和 end2 都大于 end1),则结束 foreach 循环

    非常感谢您的时间和反馈!

    【讨论】:

    • 如果我理解正确的话,%{$CNV{$j}} 中的键只是 CNV,没有任何开始/结束信息。那你怎么排序呢?
    • CNV 哈希看起来像这样:$CNV{$chromosome}{$start,$end}。结束和开始哈希具有相同的键($CNVstart{$chromosome}{$start,$end} 和 $CNVend{$chromosome}{$start,$end})并包含开始整数($start)和结束整数($end) 分别。
    【解决方案6】:

    您将 j 取出到外部循环中的优化很好,但解决方案仍远未达到最佳状态。

    您的问题确实有一个简单的 O(N+M) 解决方案,其中 N 是 CNV 的总数,M 是重叠的数量。

    这个想法是:您遍历 DNA 的长度,同时跟踪所有“当前”的 CNV。如果您看到一个新的 CNV 开始,则将其添加到列表中,并且您知道它与当前列表中的所有其他 CNV 重叠。如果您看到 CNV 结尾,只需将其从列表中删除即可。

    我不是一个非常优秀的 perl 程序员,所以将以下内容视为伪代码(它更像是 Java 和 C# 的混合 :)):

    // input:
    Map<CNV, int> starts;
    Map<CNV, int> ends;
    
    // temporary:
    List<Tuple<int, bool, CNV>> boundaries;
    foreach(CNV cnv in starts)
        boundaries.add(starts[cnv], false, cnv);
    foreach(CNV cnv in ends)
        boundaries.add(ends[cnv], true, cnv);
    
    // Sort first by position, 
    // then where position is equal we put "starts" first, "ends" last
    boundaries = boundaries.OrderBy(t => t.first*2 + (t.second?1:0));
    
    HashSet<CNV> current;
    
    // main loop:
    foreach((int position, bool isEnd, CNV cnv) in boundaries)
    {
        if(isEnd)
            current.remove(cnv);
        else
        {
            foreach(CNV otherCnv in current)
                OVERLAP(cnv, otherCnv); // output of the algorithm
            current.add(cnv);
        }
    }
    

    【讨论】:

      【解决方案7】:

      现在我不是 perl 战士,但根据给出的信息,它在任何编程语言中都是一样的;除非您对要检查的属性的“哈希”进行排序并进行二进制查找,否则您不会提高查找的任何性能。

      如果可能的话,您还可以计算哈希中的哪些索引具有您感兴趣的属性,但由于您没有关于这种可能性的信息,这可能不是一个解决方案。

      【讨论】:

      • 第一段没有意义。哈希查找应该比二分查找更快。你也不能对哈希进行排序。您似乎混淆了哈希和数组。
      • 哈希已经有O(1) 查找。问题是 OP 正在搜索所有这些,而且有很多。
      • @musikk, @Ether: 对,所以帕特里克说要弄清楚如何查找可能感兴趣的键,然后遍历这些键,而不是遍历所有键并检查每个键以查看如果感兴趣
      • @musikk:您可以对哈希键进行排序。例如:foreach my $key (sort keys %hash){...},但是排序可能会影响另一个性能
      • @musikk:很抱歉让您感到困惑,我很清楚哈希具有O(1) 查找,并且您无法对哈希映射数据类型进行排序,但我从未另有说明。 OP想要一种检查值数组中每个元素的方法(即使它是哈希映射),除非对值进行排序,否则他需要进行线性查找,即必须对键或属性进行排序才能做到二进制搜索。 @ysth 做对了……
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-17
      • 2017-07-14
      • 2020-10-11
      • 1970-01-01
      • 2010-10-17
      • 1970-01-01
      相关资源
      最近更新 更多