2016-02-27 44 views
2

我有这个脚本,它基于相同的部分组合了两个文本。有什么办法来加速这个Perl脚本?

use warnings; 
use strict; 
use utf8; 
use open ':encoding(utf8)'; 
binmode(STDOUT, ":utf8"); 

my $f1 = 'input.txt'; 
my $f2 = 'add.txt'; 
my $f3 = 'output.txt'; 

my %ids; 
my $fh; 

open $fh, '<', $f2 or die "Can't read the file with replacements: $!"; 
while (<$fh>) { 
    chomp; 
    my ($name, $id) = split /=/; 
    $ids{$name} = $id; 
} 
close $fh; 

open my $fho, '>', $f3 or die "Can't write output file: $!"; 
open $fh, '<', $f1 or die "Can't read input file: $!"; 
while (<$fh>) { 
    for my $name (keys %ids) { 
     s/$name/${name} $ids{$name}/; 
    } 
    print $fho $_; 
} 

close $fh; 
close $fho; 

例如,

input.txt中 - “文本流” 没有特别的结构

random text random text, TARGET TEXT 1 — random 
textTARGET TEXT 2! random text random text 
random text random text random text 
TARGET TEXT 3 random text random text TARGET TEXT 4 random text 

add.txt - 文本的方式来加入

TARGET TEXT 1=ADDITIONAL TEXT 1 
TARGET TEXT 2=ADDITIONAL TEXT 2 
TARGET TEXT 3=ADDITIONAL TEXT 3 
TARGET TEXT 4=ADDITIONAL TEXT 4 

output.txt的将是:

random text random text, TARGET TEXT 1 ADDITIONAL TEXT 1 — random 
textTARGET TEXT 2 ADDITIONAL TEXT 2! random text random text 
random text random text random text 
TARGET TEXT 3 ADDITIONAL TEXT 3 random text random text TARGET TEXT 4 ADDITIONAL TEXT 4 

我有一个相当大的文本文件结合(〜40Mb)和脚本做它的工作超级慢。有什么方法可以加速吗?或者,也许有人知道一个可以做同样事情的工具。

+0

如果您知道每行只能进行一次替换,您可以从'for for'循环中'结束'。 – toolic

+0

你可以将所有的目标字符串合并为一个正则表达式,然后是/// ge'吗? – toolic

+0

@toolic,不幸的是,有三个匹配中的两个很多行。我刚更新了这个例子。目标文本匹配也完全不同。 – Systematis

回答

5

循环中的循环始终是可疑的,特别是当涉及到IO时。

while (<$fh>) { 
    for my $name (keys %ids) { 
     s/$name/${name} $ids{$name}/; 
    } 
    print $fho $_; 
} 

您可以在这里完成的最佳性能改进是不要逐行执行。相反,阅读整个文件并将其作为单个文本进行处理。如今,如果你把整个文件作为一个单独的字符串读取,那么你可以一次完成所有的事情。这消除了大量的Perl和IO开销。

# Or use File::Slurp or Path::Tiny 
my $text = do { local $/; <$fh> }; 

for my $name (keys %ids) { 
    # The /g is important to replace all instances of each key 
    $text =~ s/$name/${name} $ids{$name}/g; 
} 
print $fho $text; 

聪明的缓冲可以提高内存的效率。您可以使用read()来读取大块中的文件,同时确保$text总是以换行符结尾,而不是读取整个文件。阅读文件的一般技巧值得自己的问题,可能已经有了答案,所以我把它留给你。


接下来的改进是不循环每个键。相反,将所有的键组合成一个正则表达式,获得每行匹配的所有键,然后应用它们。使用Regex::Assemble进行组合。

my $all_keys = Regexp::Assemble->new; 
$all_keys->add(keys %ids); 
my $all_keys_re = $all_keys->re; 

# Get all the matched keys at once, the /g is important. 
my @matches = $text =~ /($all_keys_re)/g; 

# Replace all the matched keys. Use uniq to avoid doing the replacement twice. 
for my $match (uniq @matches) { 
    # Use /g to replace multiple copies of the same key on a line. 
    $text =~ s/$match/$match $ids{$match}/g; 
} 
print $fho $text; 

如果每个文件包含的可能键总数很低,这将是一场胜利。正则表达式将显着加快,因为它将使用比蛮力重新扫描每个键的文本更高效的算法。它也将在通常比Perl字节码更高效的正则表达式引擎中执行。

通过使用来自其他答案的建议并在单个s///中完成所有这些操作,可以实现更高的效率。

my $text = do { local $/; <> }; 

$text =~ s{($all_keys_re)}{$1 $ids{$1}}g; 

print $text; 
+0

谢谢你这么全面的回答。 'my $ text = do {local $ /; <$fh>};'做了它的工作。我已经在示例文本上测试了脚本,它比以前快了21倍(14s vs 304s)!此外,我尝试使用Regex :: Assemble,但由于某种原因,此脚本与它(39s)的工作速度较慢。 – Systematis

+1

如果您发现替换'TARGET TEXT 1'也会消耗'TARGET TEXT 10'以及更多,那么速度提高7倍而不是21倍似乎是有价值的。只是一个例子。 Schwern正在结合正则表达式持续发出明智的声音 - 注意。 –

1

串连你的模式(密钥)转换成一个大的正则表达式:

/(a|b|c|d|...|zzz)/ 

编译正则表达式大过一次,并使用该组$1在查找您的钥匙。

s/$big_re/$1 . $addtext{$1}/ge; 

(该/e标志使得更换一种表达,而非文字。你正在写$1 . $text,但可能要表达的范围内做其他事情(调用一个函数,使之低的情况下,增加更多的格式,等等。 。)查看文档here,寻找/e标志的例子

+1

组合正则表达式时有许多微妙的边缘情况。我强烈建议[Regexp :: Assemble](https://metacpan.org/pod/Regexp::Assemble)来做到这一点。 – Schwern

1

这一点,你可以很轻易加速:

for my $name (keys %ids) { 
    s/$name/${name} $ids{$name}/; 
} 

它编译成一个正则表达式:

my $search = join "|", map {quotemeta} keys %ids; 
    $search = qr/\b($search)\b/; 

然后在循环:

s/$search/$1 $ids{$1}/g; 

注意 - 我已经添加\b一个字打破匹配,因为这是不太可能你绊倒了子和排序顺序。显然,你不需要。

但它意味着你不是然后做循环的正则表达式匹配每次迭代。

+1

组合正则表达式时有许多细微的边缘情况。我强烈建议[Regexp :: Assemble](https://metacpan.org/pod/Regexp::Assemble)来做到这一点。 – Schwern

0

虽然它看起来似是而非,以上答案都假定替代模式的应用是独立于add.txt定义模式的顺序。

原来的问题应该澄清更多以正确回答。

例如,<b> input.txt </b> can be changed only once

如果在add.txt一个图案改变一些行,然后在add.txt其他图案改变上次改变的呢?