【问题标题】:Hash incorrectly tracking counts, runtime long哈希错误跟踪计数,运行时间长
【发布时间】:2017-03-30 04:01:11
【问题描述】:

我正在使用 Perl 编写一个程序,但我的输出是错误的,并且需要很长时间才能处理。该代码旨在接收一个大型 DNA 序列文件,以 15 个字母增量 (kmers) 读取它,一次向前走 1 个位置。我应该将 kmer 序列输入到哈希中,它们的值是该 kmer 的出现次数——这意味着每个键应该是唯一的,当找到重复项时,它应该增加该特定 kmer 的计数。我从我的教授预期的输出文件中知道,我有太多的行,所以它允许重复并且不正确计数。它也运行了 5 分钟以上,所以我必须 Ctrl+C 才能逃脱。当我查看 kmers.txt 时,该文件至少已正确写入和格式化。

#!/usr/bin/perl

use strict;
use warnings;
use diagnostics;

# countKmers.pl
# Open file /scratch/Drosophila/dmel-2L-chromosome-r5.54.fasta
# Identify all k-mers of length 15, load them into a hash
# and count the number of occurences of each k-mer. Each
# unique k-mer and its' count will be written to file
# kmers.txt

#Create an empty hash
my %kMersHash = ();

#Open a filehandle for the output file kmers.txt
unless ( open ( KMERS, ">", "kmers.txt" ) ) {
    die $!;
}

#Call subroutine to load Fly Chromosome 2L
my $sequenceRef = loadSequence("/scratch/Drosophila/dmel-2L-chromosome-r5.54.fasta");

my $kMer      = 15;    #Set the size of the sliding window

my $stepSize  = 1;     #Set the step size

for (

    #The sliding window's start position is 0
    my $windowStart = 0;

    #Prevent going past end of the file
    $windowStart <= ( length($$sequenceRef) - $kMer );

    #Advance the window by the step size
    $windowStart += $stepSize

    )

{

    #Get the substring from $windowStart for length $kMer
    my $kMerSeq = substr( $$sequenceRef, $windowStart, $kMer );

#Call the subroutine to iterate through the kMers
    processKMers($kMerSeq);

}

sub processKMers {

    my ($kMerSeq) = @_;

    #Initialize $kCount with at least 1 occurrence  
    my $kCount = 1;

    #If the key already exists, the count is  
    #increased and changed in the hash
    if ( not exists $kMersHash{$kMerSeq} ) {

            #The hash key=>value is loaded: kMer=>count
            $kMersHash{$kMerSeq} = $kCount;
    }

    else {

            #Increment the count 
            $kCount ++;

            #The hash is updated 
            $kMersHash{$kMerSeq} = $kCount;
    }

    #Print out the hash to filehandle KMERS
    for (keys %kMersHash) {
            print KMERS $_, "\t", $kMersHash{$_}, "\n";
    }
}

sub loadSequence {

    #Get my sequence file name from the parameter array
    my ($sequenceFile) = @_;

    #Initialize my sequence to the empty string
    my $sequence = "";

    #Open the sequence file
    unless ( open( FASTA, "<", $sequenceFile ) ) {
            die $!;
    }

    #Loop through the file line-by-line
    while (<FASTA>) {

            #Assign the line, which is in the default 
            #variable to a named variable for readability.
            my $line = $_;

            #Chomp to get rid of end-of-line characters
            chomp($line);

            #Check to see if this is a FASTA header line
            if ( $line !~ /^>/ ) {

                    #If it's not a header line append it 
                    #to my sequence
                    $sequence .= $line;
            }

    }

    #Return a reference to the sequence
    return \$sequence;
}

【问题讨论】:

  • 您正在处理/输出每一步。您可能想先完成这些步骤,然后输出您的哈希值。您只需执行$kMersHash{$kMerSeq}++; 即可简化计数,因为 perl 会自动激活(如果不存在则创建)您的哈希键。
  • 我知道它必须是我的 processKMers 子例程中的某些内容重复且效率低下,我只是不知道是什么。
  • 这是因为您每次调用processKMers 时都在printing 密钥,这是针对每一步的。将打印逻辑移出子程序,在处理完整个字符串后只执行一次。
  • @xxfelixxx 所以你是说我可以完全取出我的 if else 并增加?
  • 是的。这是一篇关于它的好文章:effectiveperlprogramming.com/2011/04/…

标签: perl hash bioinformatics


【解决方案1】:

以下是我将如何编写您的应用程序。 processKMers 子例程归结为只是增加一个哈希元素,所以我已经删除了它。我还更改了标识符以匹配在 Perl 代码中更常见的 snake_case,并且我没有看到 load_sequence 返回对序列的引用的任何点,因此我将其更改为返回字符串本身

use strict;
use warnings 'all';

use constant FASTA_FILE => '/scratch/Drosophila/dmel-2L-chromosome-r5.54.fasta';
use constant KMER_SIZE  => 15;
use constant STEP_SIZE  => 1;

my $sequence = load_sequence( FASTA_FILE );

my %kmers;

for (my $offset = 0;
        $offset + KMER_SIZE <= length $sequence;
        $offset += STEP_SIZE ) {

    my $kmer_seq = substr $sequence, $start, KMER_SIZE;

    ++$kmers{$kmer_seq};
}

open my $out_fh, '>', 'kmers.txt' or die $!;

for ( keys %kmers ) {
    printf $out_fh "%s\t%d\n", $_, $kmers{$_};
}

sub load_sequence {

    my ( $sequence_file ) = @_;

    my $sequence = "";

    open my $fh, '<', $sequence_file or die $!;

    while ( <$fh> ) {
        next if /^>/;
        chomp;
        $sequence .= $_;
    }

    return $sequence;
}

这是增加哈希元素的一种更简洁的方法,无需直接在哈希上使用++

my $n;

if ( exists $kMersHash{$kMerSeq} ) {
    $n = $kMersHash{$kMerSeq};
}
else {
    $n = 0;
}

++$n;
$kMersHash{$kMerSeq} = $n;

【讨论】:

  • 感谢您的努力。我学习 Perl 才 10 周,所以 cmets 和代码风格反映了这一点。您更改为常量的变量不合适,因为 kmers 因查询而异,并且代码是可重用的。
  • @Michelle_M:我声明为常量的值仅在程序的一次运行中是常量。它们并不比你自己的$kMer$stepSize 更固定。
  • 我明白,只是随附的讲座表明我们不应该将它们设为常量。
  • @Michelle_M:啊。如果这是一个家庭作业问题,那么你应该这么说。你会得到一个非常不同风格的答案。
  • 对不起,我认为我提到我的教授的问题中暗示了这一点。只是想从新手的角度来解决这些问题。
【解决方案2】:

除了processKMers 之外,您的代码中的一切看起来都很好。主要问题:

  • $kCount 在对 processKMers 的调用之间不持久,因此在您的 else 语句中,$kCount 将始终为 2

  • 每次调用 processKMers 时都会打印,这会拖慢您的速度。经常打印会显着减慢您的进程,您应该等到程序结束再打印一次。

保持您的代码基本相同:

sub processKMers {

    my ($kMerSeq) = @_;

    if ( not exists $kMersHash{$kMerSeq} ) {
            $kMersHash{$kMerSeq} = 1;
    }
    else {
            $kMersHash{$kMerSeq}++;
    }
}

然后你想将你的打印逻辑移动到你的 for 循环之后。

【讨论】:

  • 感谢您完好无损地保留了我的初学者风格代码。正如我和其他回答者所说,我只有 10 周的时间来学习 Perl。有没有更“补救”的方法来增加哈希?我们还没有学会将 ++ 与哈希一起使用。
  • 您可以将++ 与任何标量一起使用,但它与$kMersHash{$kMerSeq} + $kMersHash{$kMerSeq} + 1; 相同。
  • 呃 - 应该是 $kMersHash{$kMerSeq} = $kMersHash{$kMerSeq} + 1;。我猜我不能编辑 cmets。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2013-12-04
  • 2020-03-15
相关资源
最近更新 更多