在Perl中对大型哈希表进行排序

3
我正在分析在句子中连续出现的一组单词的出现频率。
每个组包括三个单词,我们需要计算它们的频率。
例如:This is a good time to party because this is a vacation time.
期望输出结果:
this is a - 2
is a good - 1
a good time - 1

等等其他内容。

我编写了一个脚本,它工作得很好,并打印出按降序排列的频率。

它通过逐行读取文件来工作。对于每一行,它将其转换为小写,将其分割成单个单词,然后形成一个数组。

然后,我们从左侧开始每次选择3个单词,并继续形成存储出现次数计数的哈希表。完成后,我们移动数组中最左边的元素并重复此过程,直到我们的数组包含超过3个单词为止。

问题更新:

问题是,我想在包含超过1000万行的文件上使用此脚本。

经过一些测试,我发现如果输入文件中的行数超过400K,则它将无法工作。

如何使此脚本更加内存高效?

感谢fxzuz提供的建议,但现在我想使此脚本适用于更大的文件:)

#!/usr/bin/perl

use strict;
use warnings;

print "Enter the File name: ";
my $input = <STDIN>;
chomp $input;

open INPUT, '<', $input 
    or die("Couldn't open the file, $input with error: $!\n");

my %c;

while (my $line = <INPUT>) {

    chomp $line;
    my @x = map lc, split /\W+/, join "", $line;

    while (@x>3) {

        $c{"@x[0..2]"}++;
        shift @x;
    }
}

foreach $key (sort {$c{$b} <=> $c{$a}} keys %c) {

    if($c{$key} > 20) {

        print $key." - ".$c{$key}."\n";
    }
}

close INPUT;

这段代码效果很好,将按词组出现频率降序打印。只会打印那些出现超过20次的词组。
那么如何处理超过100万或1000万行的文件?
我还使用Linux的top命令来检查perl运行此脚本时的内存和CPU使用情况,并观察到在处理包含40万行的文件时,CPU使用率达到了100%,而内存使用率接近90%。
因此,对于包含一百万行的文件,无法使其正常工作,因为perl进程将会挂起。
如何使这段代码更节省内存?
2个回答

3

您在声明和使用变量方面遇到了一些问题。请在您的脚本中添加Pragma use strict。在使用哈希表进行for循环块等操作时,请使用局部变量。我注意到您有语句if($c{$key} > 20),但哈希表的值小于等于2。

#!/usr/bin/perl

use strict;

my %frequency;

while (my $line = <DATA>) {

    chomp $line;
    my @words = map lc, split /\W+/, $line;

    while (@words > 3) {

        $frequency{"@words[0,1,2]"}++;
        shift @words;
    }
}

# sort by values
for my $key (sort {$frequency{$b} <=> $frequency{$a}} keys %frequency) {

    printf "%s - %s\n", $key, $frequency{$key};
}                                                                                   

__DATA__
This is a good time to party because this is a vacation time.

输出

this is a - 2
to party because - 1
is a good - 1
time to party - 1
party because this - 1
because this is - 1
good time to - 1
is a vacation - 1
a good time - 1

嗨。我尝试了这个,但当你读取多行时它不起作用。我有大约600K句子在我正在处理的文本文件中。它不起作用。它会卡住。你能否尝试在一个由多个句子组成的文件上运行你的脚本? - Neon Flash
600K句子似乎很多。您确定有足够的内存执行排序吗? - mvp
你说得对。当我处理较少的句子时,我的初始代码表现良好。有没有一种编写内存高效代码的方法? - Neon Flash
@NeonFlash 关键是将结构体移出内存,把它们存储到磁盘上和/或数据库中都应该被考虑。 - user289086

3
显然,你的代码编写正确并且可以工作,但只有在你的数据集不是非常大的情况下才能正常运行。如果你有很多输入数据(似乎你确实有),由于内存不足,排序阶段可能会失败。如果你无法增加内存,唯一的解决方案是将数据以文本或数据库格式写入磁盘。
1. 文本格式:你可以简单地将三元组按行写入文本文件中,每行一个三元组。这样做会使输出大小增加3倍,但仍应该可以处理。然后,你可以使用命令行gnu sort和uniq工具来获取所需的计数,类似于以下内容: text2triplet.pl
2. 数据库格式:使用DBD::SQLite并创建如下表格: CREATE TABLE hash (triplet VARCHAR, count INTEGER DEFAULT 0); CREATE INDEX idx1 ON hash (triplet); CREATE INDEX idx2 ON hash (count);
随着你的进行,将你的三元组插入到该表中,并增加重复项的计数。处理完数据后,简单地将其...
 SELECT * FROM hash
 WHERE count > 20
 ORDER BY count DESC

并将其打印出来。 然后,您可以DROP哈希表或仅删除整个SQLite数据库。

这两种方法都应该允许您扩展到几乎与磁盘大小相同的规模,但数据库方法可能更加灵活。


1
你好。感谢您提供的解决方案。我不太理解文本格式的命令行。text2triplet.pl是Perl脚本,它将把输入文件中的每个句子解析成三元组。input.txt是包含数百万个需要解析的句子的文件。输出写入到哪里了?您说,将三元组存储到单独的文件中?您能详细说明一下吗? - Neon Flash
我想你的意思是说我们需要将脚本的输出,每个三元组一行写入控制台,然后将其管道传输到sort和uniq中?是的,这会非常大。所以,我会将输出写入文件中 :) - Neon Flash
1
如果您有一个包含600K行的文件,每行100字节(60MB),输出文件只有180MB - 对于排序来说不是非常大,并且甚至可以使用管道。我认为这对您来说应该运行得相当快。关于sort -r的另一个注意事项:请使用sort -r -k1 -n(按字段1进行数字排序)。 - mvp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接