[英]more efficient way to extract lines from a file whose first column matches another file
我有两个文件,目标文件和清理文件。
我想提取目标行与干净的第一行相匹配。
我编写了一个基于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 的sed
和grep
答案相似,除了不是很多正则表达式,只有一个。
我不想更改已经编写的子例程,因此我添加了一个通过在命令行中添加--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.