Perl 使用闭包作为迭代器

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

我最近一直在玩马尔可夫链,试图从一个大型语料库生成文本,只是为了看看我得到了什么(其中一些很有趣)

构建文本生成所需的数据结构的很大一部分是创建。给出一个小样本文本:“今天是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 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;
   }
}