繁体   English   中英

从第一列与另一个文件匹配的文件中提取行的更有效方法

[英]more efficient way to extract lines from a file whose first column matches another file

我有两个文件,目标文件和清理文件。

  • 目标有一些1055772行,每行有3000列,用制表符分隔。 (大小为7.5G)
  • “清除”稍短一些,为806535。“清除”只有一列,与“目标”第一列的格式匹配。 (大小为1300万)

我想提取目标行与干净的第一行相匹配。

我编写了一个基于grep的循环来执行此操作,但是速度很慢。 提速将获得奖励和/或笑脸奖励。

clean  = "/path/to/clean"
target = "/path/to/target"
oFile  = "/output/file"

head -1 $target > $oFile
cat $clean | while read snp; do
    echo $snp
    grep $snp $target >> $oFile
done

$ head $clean
1_111_A_G
1_123_T_A
1_456_A_G
1_7892_C_G

编辑:编写了一个简单的python脚本来做到这一点。

 clean_variants_file = "/scratch2/vyp-scratch2/cian/UCLex_August2014/clean_variants"

allChr_file = "/scratch2/vyp-scratch2/cian/UCLex_August2014/allChr_snpStats"

outfile = open("/scratch2/vyp-scratch2/cian/UCLex_August2014/results.tab","w")

 clean_variant_dict = {}


for line in open(clean_variants_file):

clean_variant_dict[line.strip()] = 0


for line in open(allChr_file):

ll = line.strip().split("\t")

id_ = ll[0]

if id_ in clean_variant_dict:

    outfile.write(line)



 outfile.close()

该Perl解决方案将使用大量内存(因为我们将整个文件加载到内存中),但可以避免两次循环。 它使用散列进行重复检查,其中每行都存储为键。 请注意,此代码尚未经过全面测试,但似乎只能在有限的一组数据上使用。

use strict;
use warnings;

my ($clean, $target) = @ARGV;

open my $fh, "<", $clean or die "Cannot open file '$clean': $!";

my %seen;
while (<$fh>) {
    chomp;
    $seen{$_}++;
}

open $fh, "<", $target 
        or die "Cannot open file '$target': $!";    # reuse file handle

while (<$fh>) {
    my ($first) = /^([^\t]*)/;
    print if $seen{$first};
}

如果您的目标文件是使用制表符分隔的CSV数据,则可以使用Text::CSV_XS ,据说速度非常快。

python解决方案:

with open('/path/to/clean', 'r') as fin:
    keys = set(fin.read().splitlines())

with open('/path/to/target', 'r') as fin, open('/output/file', 'w') as fout:
    for line in fin:
        if line[:line.index('\t')] in keys:
            fout.write(line)

使用perl单线:

perl -F'\t' -lane '
    BEGIN{ local @ARGV = pop; @s{<>} = () }
    print if exists $s{"$F[0]\n"}
  ' target clean

开关:

  • -F-a开关的备用模式
  • -l :启用行结束处理
  • -a :在空间上分割行并将其加载到@F数组中
  • -n :为输入文件中的每个“行”创建一个while(<>){...}循环。
  • -e :告诉perl在命令行上执行代码。

或作为perl脚本:

use strict;
use warnings;

die "Usage: $0 target clean\n" if @ARGV != 2;

my %s = do {
    local @ARGV = pop;
    map {$_ => 1} (<>)
};

while (<>) {
    my ($f) = split /\t/;
    print if $s{"$f\n"}
}

为了好玩,我想我可以将一个或两个解决方案转换为Perl6。

注意:在Rakudo / NQP获得更多优化之前,这些方法可能会比原始方法慢,而这些优化实际上只是在发布时才认真开始的。

  • 首先是TLP的 Perl5答案几乎一对一地转换为Perl6。

     #! /usr/bin/env perl6 # I have a link named perl6 aliased to Rakudo on MoarVM-jit use v6; multi sub MAIN ( Str $clean, Str $target ){ # same as the Perl5 version MAIN( :$clean, :$target ); # call the named version } multi sub MAIN ( Str :$clean!, Str :$target! ){ # using named arguments note "Processing clean file"; my %seen := SetHash.new; for open( $clean, :r ).lines -> $line { next unless $line.chars; # skip empty lines %seen{$line}++; } note "Processing target file"; for open( $target, :r ).lines -> $line { $line ~~ /^ $<first> = <-[\\t]>+ /; say $line if %seen{$<first>.Str}; } } 

    我使用了MAIN子例程,以便在未提供正确参数的Usage获得“ Usage消息。
    我还使用了SetHash而不是常规的Hash来减少内存使用,因为我们不需要知道已经找到了多少,只知道它们被发现了。

  • 接下来,我尝试将干净文件中的所有行合并到一个正则表达式中。

    这与Cyrus sedgrep答案相似,除了不是很多正则表达式,只有一个。

    我不想更改已经编写的子例程,因此我添加了一个通过在命令行中添加--single-regex-s来区别的子例程。 (所有示例都在同一个文件中)

     multi sub MAIN ( Str :$clean!, Str :$target!, Bool :single-regex(:s($))! ){ note "Processing clean file"; my $regex; { my @regex = open( $clean, :r ).lines.grep(*.chars); $regex = /^ [ | @regex ] /; } # throw away @regex note "Processing target file"; for open( $target, :r ).lines -> $line { say $line if $line ~~ $regex; } } 

我会说,与在Perl5中编写代码相比,我花费了更长的时间来编写代码。 大部分时间都花在网上搜索一些成语,然后查看Rakudo的源文件。 我认为要在Perl6上取得比Perl5更好的成绩不需要花费太多的精力。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM