2014-03-06 1 views
3

최근 마르코프 체인을 가지고 놀았는데, 큰 코퍼스에서 무엇을 얻었는지 알기 위해 텍스트를 생성하려고했습니다 (그 중 일부는 상당히 흥미 롭습니다).클로저를 반복자로 사용

텍스트 생성에 필요한 데이터 구조를 구축하는 데는 많은 부분이 n-grams입니다. n = 3가 될 것이다 "오늘 목요일 3 월 여섯 번째는"예를 들어 N-g : 작은 샘플 텍스트 감안할 때

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 

를 텍스트의 크기, 내 코드에 의해 생성 된 N-g의 목록에 따라 수 상당히 크다. 일부 언어에서는 사용자 정의 반복자를 만드는 yield 문을 포함하는 generator이라는 개념이 있지만 Perl은 불행히도 그 중 하나가 아닙니다.

대신 Perl에서 어휘 변수에 대한 클로저를 사용하여 Iterators을 만들 수 있지만 사용시 실제로 얻고있는 것을 이해하는 데 약간의 어려움이 있습니다. 여기

내가 N-그램을 생성하기 위해 만든 반복자 (즉, n은에서 열리는 가정 $자가> 순서) :

sub _ngrams { 
    my ($self, @words) = @_; 

    return sub { 
     while(@words) { 
     my @ngram = @words[0 .. $self->order]; # get $order + 1 words 
     shift @words;       # drop the first word 

     return @ngram; 
     } 

     return; # nothing left to do 
    }; 
} 

난 정말이 코드의 효율성이 많다는에서 무엇을 얻을 수 있습니까? 단어 목록은 여전히 ​​전체적으로 메모리에 @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]; 
     } 
    } 
} 

모든 입력이 매우 도움이 될 것입니다.

+1

반복기를 사용하면 유연성을 얻을 수 있습니다. 스트림에서 단어를 제공하는 반복기에서 쉽게 바꿀 수 있습니다. (나는 n-gram을 반환하는 반복자가 없을 것이고, 단어를 반환하는 반복자를 가질 것입니다.) – ikegami

+0

@ikegami 그래도이 시나리오에서 작동합니까? N + 1 단어를 가져와야하는 경우 첫 번째 단어 만 제거하십시오. 그런 다음 이전 N 단어를 포함하는 다음 N + 1 단어를 가져옵니다. –

+0

이미 가지고있는 논리를 사용하고 반복자 밖으로 이동하십시오. – ikegami

답변

2

우선, 반복기 구현은 마지막 몇 가지 값의 undef 개 항목을 반환하는 나쁜 경향이 있습니다. 나는이 반복자는 좋은 추상화입니다

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 
    }; 
} 

다음으로 변경할 것입니다. 어떤 식 으로든 성능을 향상시키려는 것이 아니라 기본 코드를 더 간단하게 만드는 것이 유용합니다. 여기에서는 반복을 분리하지 않고 주 코드에서 모두 수행하면 코드가 더 짧아집니다 (단순하지는 않습니다).

그러나 반복기는 지연 평가 또는 무한한 스트림과 같은 흥미로운 것을 처리 할 수 ​​있습니다. 이 유용하다는 것을, 우리는 이상 완전히 스트림으로 전환해야 할 것 :

my $text = $self->text; 
my $iter = $self->_ngrams(sub { 
    return $1 if $text =~ /\G\s*(\S+)/gc; 
    return; 
}); 

처럼 초기화 될

# 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; 
    }; 
} 

이 여기 유용합니까? 반복자에서 모든 요소를 ​​즉시 가져 오므로 아니요. 간단한 솔루션은 더 멋진 추상화를 사용하지 않으며, 단순히이 될 것이다 :

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 해시가 자동 활성화되므로 exists을 테스트 할 필요가 없습니다. (아니면 이상한 확장 기능을 사용하고 있습니까?)

+0

고마워, 정말 유용 했어. 마지막 예제는 프로세스를 분할하는 것보다 훨씬 깔끔하게 보입니다. 하나는 작지 않은데, 그 값은 실제로 n-gram의 마지막 부분입니다. 그래서 나는 그것을 꺼 냈습니다. 나는 또한이 글을 쓰기 전에 고등 Perl의 반복자에 관한 장을 읽고 그것을 사용하는 것이 가렵다. :) –

관련 문제