3

我正在分析在一个句子中一起出现的词组的出现频率。

每组由 3 个单词组成,我们必须计算它们的频率。

示例:这是聚会的好时机,因为这是假期。

预期输出:

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 使用率,并观察到当脚本在由 400K 行组成的文件上运行时,CPU 使用率达到 100%,内存使用率接近 90%。

因此,让它与一个由 100 万行组成的文件一起工作是不可行的。因为perl进程会挂起。

我怎样才能让这段代码更有效地使用内存?

4

2 回答 2

3

您在声明和使用变量方面存在一些问题。use strict请在您的脚本中添加编译指示。当您使用散列for block和其他时使用局部变量。我注意到您有声明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
于 2012-10-24T05:57:16.137 回答
3

显然,您的代码编写正确并且可以工作,但前提是您的数据集不是很大。如果您有大量输入数据(并且看起来确实如此),则排序阶段可能会由于内存不足而失败。如果您无法增加内存,唯一的解决方案是将数据写入磁盘 - 以文本或数据库格式。

  1. 文本格式:您可以在进入文本文件时简单地编写您的三元组,每个三元组一行。这样做会将输出大小增加 3 倍,但它仍然应该是可控的。然后,您可以简单地使用命令行 gnu sort 和 uniq 工具来获得所需的计数,如下所示:

    text2triplet.pl <input.txt | sort | uniq -c | sort -r | head -10000 (您可能希望将输出存储到一个文件中,如果它很大,则不要通过管道传输它)

  2. 数据库格式:使用 DBD::SQLite 并创建如下表:

    CREATE TABLE hash (triplet VARCHAR, count INTEGER DEFAULT 0);

    CREATE INDEX idx1 ON hash (triplet);

    CREATE INDEX idx2 ON hash (count);

INSERT你走的时候把你的三胞胎放到那个桌子上,并增加重复的次数。数据处理完毕后,只需

 SELECT * FROM hash
 WHERE count > 20
 ORDER BY count DESC

并打印出来。然后你可以DROP你的哈希表或者干脆完全删除整个 SQLite 数据库。

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

于 2012-10-24T09:43:36.397 回答