Чувствительность к регистру в Perl-скрипте - как сделать его нечувствительным? - PullRequest
1 голос
/ 03 марта 2010

Как бы изменить следующий скрипт markov, чтобы прописные и строчные слова обрабатывались одинаково?

Вся идея состоит в том, чтобы помочь повысить качество вывода моего генератора текста markov.

В нынешнем виде, если в него вставить 99 строчных предложений и 1 заглавное предложение, вы почти всегда найдете немарковизированную версию заглавного предложения в выводе.

# Copyright (C) 1999 Lucent Technologies
# Excerpted from 'The Practice of Programming'
# by Brian W. Kernighan and Rob Pike

# markov.pl: markov chain algorithm for 2-word prefixes

$MAXGEN = 10000;
$NONWORD = "\n";
$w1 = $w2 = $NONWORD;                    # initial state
while (<>)
{                                        # read each line of input
    foreach (split)
    {
      push(@{$statetab{$w1}{$w2}}, $_);
      ($w1, $w2) = ($w2, $_);        # multiple assignment
    }
}

push(@{$statetab{$w1}{$w2}}, $NONWORD);  # add tail
$w1 = $w2 = $NONWORD;

for ($i = 0; $i < $MAXGEN; $i++) 
{
    $suf = $statetab{$w1}{$w2};      # array reference
    $r = int(rand @$suf);            # @$suf is number of elems
    exit if (($t = $suf->[$r]) eq $NONWORD);
    print "$t\n";
    ($w1, $w2) = ($w2, $t);          # advance chain
}

Ответы [ 3 ]

6 голосов
/ 03 марта 2010

Натан Феллман и Мобруле оба предлагают общую практику: Нормализация .

Часто проще обрабатывать данные, чтобы они соответствовали ожидаемым нормам содержания и структуры, прежде чем выполнять фактические вычисления, которые являются главной целью программы или подпрограммы.

Марковская цепная программа была интересной, поэтому я решил поиграть с ней.

Вот версия, которая позволяет вам контролировать количество слоев в цепочке Маркова. Изменяя $DEPTH, вы можете настроить порядок симуляции.

Я разбил код на подпрограммы многократного использования. Вы можете изменить правила нормализации, изменив процедуры нормализации. Вы также можете создать цепочку на основе определенного набора значений.

Код для создания многослойной таблицы состояний был самым интересным. Я мог бы использовать Data :: Diver, но я хотел сам разобраться с этим.

Код нормализации слова действительно должен позволить нормализатору возвращать список слов для обработки, а не просто одно слово - но я не чувствую необходимости исправлять его теперь может возвращать список слова .. Другие вещи, такие как сериализация вашего обработанного корпуса, были бы хороши, и использование Getopt :: Long для ключей командной строки осталось сделать. Я только сделал забавные биты.

Мне было немного сложно написать это без использования объектов - это действительно было хорошим местом для создания объекта генератора Маркова. Мне нравятся предметы. Но я решил сохранить код процедурный, чтобы он сохранил дух оригинала.

Веселитесь.

#!/usr/bin/perl
use strict;
use warnings;

use IO::Handle;

use constant NONWORD => "-";
my $MAXGEN = 10000;
my $DEPTH  = 2;

my %state_table;

process_corpus( \*ARGV, $DEPTH, \%state_table );
generate_markov_chain( \%state_table, $MAXGEN );


sub process_corpus {
    my $fh    = shift;
    my $depth = shift;
    my $state_table = shift || {};;

    my @history = (NONWORD) x $depth;


    while( my $raw_line = $fh->getline ) {

        my $line = normalize_line($raw_line);
        next unless defined $line;

        my @words = map normalize_word($_), split /\s+/, $line;
        for my $word ( @words ) {

            next unless defined $word; 

            add_word_to_table( $state_table, \@history, $word );
            push  @history, $word;
            shift @history;
        }

    }

    add_word_to_table( $state_table, \@history, NONWORD );

    return $state_table;
}

# This was the trickiest to write.
# $node has to be a reference to the slot so that 
# autovivified items will be retained in the $table.
sub add_word_to_table {
    my $table   = shift;
    my $history = shift;
    my $word    = shift;

    my $node = \$table;

    for( @$history ) {
        $node = \${$node}->{$_};
    }

    push @$$node, $word;

    return 1;
}

# Replace this with anything.
# Return undef to skip a word
sub normalize_word {
    my $word = shift;
    $word =~ s/[^A-Z]//g;
    return length $word ? $word : ();
}

# Replace this with anything.
# Return undef to skip a line
sub normalize_line {
    return uc shift;
}


sub generate_markov_chain {
    my $table   = shift;
    my $length  = shift;
    my $history = shift || [];

    my $node = $table;

    unless( @$history ) {

        while( 
            ref $node eq ref {}
                and
            exists $node->{NONWORD()} 
        ) {
            $node = $node->{NONWORD()};
            push @$history, NONWORD;
        }

    }

    for (my $i = 0; $i < $MAXGEN; $i++) {

        my $word = get_word( $table, $history );

        last if $word eq NONWORD;
        print "$word\n";

        push @$history, $word;
        shift @$history;
    }

    return $history;
}


sub get_word {
    my $table   = shift;
    my $history = shift;

    for my $step ( @$history ) {
        $table = $table->{$step};
    }

    my $word = $table->[ int rand @$table ];
    return $word;
}

Обновление: Я исправил приведенный выше код для обработки нескольких слов, возвращающихся из подпрограммы normalize_word().

Чтобы оставить регистр без изменений и рассматривать знаки препинания как слова, замените normalize_line() и normalize_word():

sub normalize_line {
    return shift;
}

sub normalize_word {
    my $word = shift;

    # Sanitize words to only include letters and ?,.! marks 
    $word =~ s/[^A-Z?.,!]//gi;

    # Break the word into multiple words as needed.
    my @words = split /([.?,!])/, $word;

    # return all non-zero length words. 
    return grep length, @words;
}

Другой большой скрывающийся недостаток в том, что я использовал - в качестве персонажа НЕ СЛОВА. Если вы хотите включить дефис в качестве символа пунктуации, вам нужно изменить определение константы NONWORD в строке 8. Просто выберите то, что никогда не может быть словом.

5 голосов
/ 03 марта 2010

Перед обработкой преобразовать все ваши входные данные в нижний регистр?

См. функцию lc .

4 голосов
/ 03 марта 2010

Я думаю, что лучшая ставка будет состоять в том, чтобы слова в нижнем регистре (или в верхнем регистре), как только они вводятся:

while (<>)
{                                        # read each line of input
    lc; # convert $_ to lowercase
    # etc.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...