Perl 使用闭包作为迭代器
我最近一直在玩马尔可夫链,试图从一个大型语料库生成文本,只是为了看看我得到了什么(其中一些很有趣) 构建文本生成所需的数据结构的很大一部分是创建。给出一个小样本文本:“今天是3月6日星期四”,其中Perl 使用闭包作为迭代器,perl,iterator,closures,Perl,Iterator,Closures,我最近一直在玩马尔可夫链,试图从一个大型语料库生成文本,只是为了看看我得到了什么(其中一些很有趣) 构建文本生成所需的数据结构的很大一部分是创建。给出一个小样本文本:“今天是3月6日星期四”,其中n=3的示例n-gram为: Today is Thursday is Thursday March Thursday March the March the sixth # skipped lines that have < 3 words because is isn't enough fo
n=3
的示例n-gram为:
Today is Thursday
is Thursday March
Thursday March the
March the sixth
# skipped lines that have < 3 words because is isn't enough for a 3-gram
我真的从这段代码中获得了什么效率方面的好处吗?单词列表仍然完全保存在@words
的内存中。是否有一种替代实现可以减少我的内存占用
以下是如何使用迭代器生成字典:
sub seed {
my $self = shift;
my $ngram_it = $self->_ngrams(split /\s+/, $self->text);
GRAM:
while (my @gram = $ngram_it->()) {
next GRAM unless @gram == scalar grep { $_ } @gram;
my $val = pop @gram;
my $key = join ' ', @gram;
if (exists $self->lexicon->{$key}) {
push @{$self->lexicon->{$key}}, $val;
}
else {
$self->lexicon->{$key} = [$val];
}
}
}
任何意见都会很有帮助 首先,迭代器实现有返回最后几个值中的
unde
项的不良倾向。我会把它改成
sub _ngrams {
my ($self, @words) = @_;
my $order = $self->order;
return sub {
if (@words > $order) {
my @ngram = @words[0 .. $order]; # get $order + 1 words
shift @words; # drop the first word
return @ngram;
}
return; # nothing left to do
};
}
接下来,这个迭代器是一个很好的抽象。它并不意味着以任何方式提高性能,它只会使主代码更简单。在这里,如果您不将迭代分离出来,并在主代码中完成所有工作,那么您的代码将更短(但不是更简单)
然而,迭代器可以处理一些有趣的事情,比如惰性计算或无限流。为了使其有用,我们必须完全切换到流:
# contract: an iterator returns a list of things
# or an empty list when depleted
sub _ngrams {
my ($self, $source) = @_;
my $order = $self->order;
my @ngram = (undef, map { $source->() } 1 .. $order);
return sub {
if (my ($next) = $source->()) {
(undef, @ngram) = (@ngram, $next); # or instead: shift/push
return @ngram;
}
return;
};
}
它的初始化方式如下
my $text = $self->text;
my $iter = $self->_ngrams(sub {
return $1 if $text =~ /\G\s*(\S+)/gc;
return;
});
这有用吗?不会,因为您会立即从迭代器中获取所有元素。最简单的解决方案是不使用花哨的抽象,简单地说就是:
sub seed {
my $self = shift;
my @words = split /\s+/, $self->text;
my $order = $self->order;
while (@words > $order) {
my @gram = @words[0 .. $order]; # get the next n-gram
shift @words;
my $val = pop @gram;
push @{$self->lexicon->{join ' ', @gram}}, $val;
}
}
我敢打赌,这也是最(时间)性能的变体
注意:不需要测试
是否存在
,因为Perl哈希自动激活。(或者您正在使用奇怪的扩展?使用迭代器可以为您提供灵活性。您可以很容易地在一个迭代器中进行交换,该迭代器从流中提供单词。(我不会有一个返回n-gram的迭代器,我会有一个返回单词的迭代器。)@ikegami在这种情况下会工作吗?在哪里我需要得到N+1个单词,然后只删除第一个?然后抓取下一个N+1单词,其中现在包括前N个单词。只需使用您已有的逻辑,但将其移出迭代器。谢谢,这非常有用。您的最后一个示例看起来确实比分解流程更清晰。一个不小的值,实际上是n-gram的最后一部分,这就是为什么我弹出它。在写这篇文章之前,我还刚刚阅读了关于高阶Perl中迭代器的一章,非常想使用它们:)
sub seed {
my $self = shift;
my @words = split /\s+/, $self->text;
my $order = $self->order;
while (@words > $order) {
my @gram = @words[0 .. $order]; # get the next n-gram
shift @words;
my $val = pop @gram;
push @{$self->lexicon->{join ' ', @gram}}, $val;
}
}