【问题标题】:In a file/array, search for hash key, and replace it with the hash value, do this for all hash keys/values在文件/数组中,搜索哈希键,并将其替换为哈希值,对所有哈希键/值执行此操作
【发布时间】:2013-07-03 23:00:17
【问题描述】:

我在该网站上进行了搜索,但令人惊讶的是,我似乎找不到适合我的特定问题的东西。所以我想我会发布它,看看你们中一些更有经验的程序员如何解决问题。

我有一个类似文本文件的电子表格(许多行带有制表符分隔的列),我想搜索某些标签(例如脚手架1253.1_size81005.6.32799_7496)并用更简化的标签替换它们(例如脚手架1253.1a )。这些标签仅在文本文件的第一列中。我已经编写了脚本,这样我就有了一个散列,其中旧标签作为键,对应于新标签作为它们各自的值。这个哈希有大约 26000 行。所以基本上我想一一取散列键,在文本文件中搜索它们,然后用它们各自的散列值替换它们。

我有一个相当不错的服务器可用,所以如果它太复杂而无法使其成为特定于加速进程的第一列,那么没关系。

这是我目前所拥有的:

 use warnings;  



$gtf = './Hc_genome/Hc_rztk_1+2+8+9.augustus.gtf'; 
    open(FASTAFILE2, $gtf);
    @gtfarray = <FASTAFILE2>;
    #print @gtfarray;


my %hash;
while (<>)
{
   chomp;
   my ($key, $val) = split /\t/;
   $hash{$key} .= exists $hash{$key} ? ",$val" : $val;
}

#print %hash;

while (my ($find, $replace) = each %hash) {
    foreach (@gtfarray){
        $_ =~ s/$find/$replace/g;
        push @newgtf, $_;   
    }
}
print @newgtf;

此代码似乎无法正常工作,因为它没有完成。我很确定这是 foreach 循环结构的问题。抱歉,我不知道有任何其他方法可以做到这一点。有没有人有更好的方法来运行这个文件并进行替换?

任何意见将不胜感激! 谢谢,

安德鲁

@DVK

这是您的 mod 的完整脚本,您的 while 循环遇到语法错误,知道为什么它不接受它吗?再次感谢!

use warnings;  

$gtf = './Hc_genome/Hc_rztk_1+2+8+9.augustus.gtf'; 
    open(FASTAFILE2, $gtf);

my %hash;
while (<>){
    chomp;
    my ($key, $val) = split /\t/;
    $hash{$key} .= exists $hash{$key} ? ",$val" : $val;
}


while $line (<FASTAFILE2>){
    my @fields = split(/\t/, $line);
    # If you only care about first column, don't need the foreach loop below;
    #    just do the loop insides on $fields[0]
    foreach my $field (@fields) {
        $field = $hash{$field} if exists $hash{$field};
        print $outfile "$field\t"; # Small bug - will print training \t
    }
    print $outfile "\n"
}

__END__

这里是语法错误: perl gtf_mod2.pl <. gtf_mod2.pl>

【问题讨论】:

  • 您必须将$line 放在括号内,例如while (my $line = &lt;FASTAFILE2&gt;)

标签: perl search hash replace bioinformatics


【解决方案1】:

您在循环中第一次使用初始的$find$replace 键/值对耗尽文件。

有两种可能的解决方案:

  1. 在while 循环的每次 迭代期间打开文件进行读取(昂贵)
  2. 将 foreach 循环移到 while 外部,每次都迭代 hash(成本更低)

示例:

REPLACE:
for my $line (@gtfarray) {
   while(my ($find, $replace) = each %hash) {
      if($line =~ s/$find/$replace/g) {
         push @newgtf, $line;
         next REPLACE; # skip to next iteration
      }
   }
   # if there was no replacement, push the old line
   push @newgtf, $line
}  

【讨论】:

  • 感谢您的回答。我现在正在运行它。正在修改的 gtf 文件的长度约为 315000 行,因此可能需要一段时间。我会让你知道结果如何。我也在尝试下面的建议,因为他们的做法与他建议的大文本文件相反,它应该花费更少的时间。
【解决方案2】:

您要替换第一列的文件有多大?

如果是 >50,000 行,你最好做 reverse

  • 遍历哈希文件一次,并将该哈希存储在内存中

  • 遍历主文件一次,对于每一行、每一列,在记忆的哈希中找到该值,如果找到则替换为哈希值,然后写入。

换句话说,删除第一个 @gtfarray = &lt;FASTAFILE2&gt;; 并将最后一个 while 循环替换为:

while my $line (<FASTAFILE2>) {
    my @fields = split(/\t/, $line);
    # If you only care about first column, don't need the foreach loop below;
    #    just do the loop insides on $fields[0]
    foreach my $field (@fields) {
        $field = $hash{$field} if exists $hash{$field};
        print $outfile "$field\t"; # Small bug - will print training \t
    }
    print $outfile "\n";
}

注意:我假设这些字段包含您的哈希键的全部内容(例如,您的数据文件将包含一个带有“scaffold1253.1_size81005.6.32799_7496”的字段,但不是一个带有“XYZscaffold1253.1_size81005.6.32799”的字段_7496___IOU")。

如果该假设是错误的,并且您确实需要运行正则表达式,因为您的脚手架字符串可能包含在较长的字符串中,那么除了运行 O(N*M) 正则表达式之外,可能还有更好的解决方案:如果您的脚手架字符串都是某种明确定义的格式(例如“scaffoldNNNNN.NNN_sizeNNNNN.NNN.NNNN_NNNN”),那么您需要做的是:

  • 对于每一行数据文件,运行一个正则表达式来查找该模式,并将整个模式放在捕获组括号内:

    @matches = ($line =~ m/(scaffold\d+\.\d+_size\d+\.\d+\.\d+_\d+/g );
    
  • 然后,在散列中查找@matches 数组的每个值。如果找到,仅将匹配项作为 s/// 正则表达式运行。

【讨论】:

  • 他说要替换的值在第一列,我认为,所以我认为foreach 循环是不必要的 - 只需检查$fields[0]
  • @ChrisCharley - 我很困惑,无论是他的意思是数据文件的第一列还是哈希文件的第一列。好点子。
  • 抱歉,是的,它是数据文件的第一列(由制表符分隔的列),其中包含需要用新标签替换的旧标签。
  • 感谢您的回答 DVK,是的,text/gtf 文件非常大(约 315000 行),所以我猜您通过哈希进行迭代的想法会更快。当我尝试运行您在上面发布的代码块时,我似乎遇到了一些语法错误,特别是在 while 循环的第一行和 while 循环的最后一行,似乎存在编译错误。我会在下面发布完整的脚本。
  • DVK,如果您不介意再帮我一些忙,请查看我上面帖子的编辑以查看我从 while 循环中得到的语法错误。知道为什么它可能会产生这个吗?再次感谢!
【解决方案3】:

这可能是Tie::File 的工作吗?假设,也就是说,数据文件可以作为一个数组来操作。

use Tie::File; 

my $file = "./Hc_genome/Hc_rztk_1+2+8+9.augustus.gtf"; 

tie @lines, 'Tie::File', $file or die ;
for (@lines) {
 s/Oldlabel/NewLable/g;   # Change this to fit
}

untie @lines ;

Tie::File 做了很多技巧来保持对文件内存的“原地”更改有效。

【讨论】:

    【解决方案4】:

    查看您的previous post,在读取文件时创建缩短的“id”不是更简单吗?那么您就不需要获取哈希的其他文件了吗?

    下面是(未经测试的)代码。 (需要将打印语句定向到命令行上的输出文件或打开文件以写入脚本)。

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my $gtf = './Hc_genome/Hc_rztk_1+2+8+9.augustus.gtf';
    open my $FASTAFILE2, "<", $gtf or die "Unable to open '$gtf' for reading. $!";
    
    my %seen;
    
    while (<$FASTAFILE2>) {
        chomp;
        my ($id, $val) = split /\t/, $_, 2;
    
        # copy $id to $prefix and
        # remove everything after '.1' in $prefix
        (my $prefix = $id) =~ s/\.1\K.*//; 
    
        if ($seen{$id}) {
            ++$seen{$id};
        }
        else {
            $seen{$id} = 'a';   
        }
        print "$prefix$seen{$id}\t$val\n";
    }
    
    close $FASTAFILE2 or die "Unable to close '$gtf' from reading. $!";
    

    【讨论】:

    • 嗯,问题是,我仍然需要自己通过上一篇文章中的代码创建的文件。而且,上一篇文章中使用的文件中的一些脚手架在此处的 text/gtf 文件中被跳过。所以我相信这会导致一些脚手架在字母表中被标记为比应有的更早的字母。所以我必须用哈希导入正确的标签。本质上,这个 gtf 文件包含上一篇文章基因组中的注释基因,并且缺少一些标签/支架,仅仅是因为有些支架没有注释基因!希望这是有道理的;)
    猜你喜欢
    • 1970-01-01
    • 2013-03-22
    • 2016-05-29
    • 2016-06-27
    • 2014-01-02
    • 2011-07-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-19
    相关资源
    最近更新 更多