Натан Феллман и Мобруле оба предлагают общую практику: Нормализация .
Часто проще обрабатывать данные, чтобы они соответствовали ожидаемым нормам содержания и структуры, прежде чем выполнять фактические вычисления, которые являются главной целью программы или подпрограммы.
Марковская цепная программа была интересной, поэтому я решил поиграть с ней.
Вот версия, которая позволяет вам контролировать количество слоев в цепочке Маркова. Изменяя $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. Просто выберите то, что никогда не может быть словом.