【问题标题】:Case Sensitivity In Perl Script - How Do I Make It Insensitive?Perl 脚本中的大小写敏感 - 如何使其不敏感?
【发布时间】:2011-01-22 22:20:58
【问题描述】:

如何更改以下马尔可夫脚本以将大写和小写单词视为相同?

整个想法是帮助提高我的马尔可夫文本生成器的输出质量。

就目前而言,如果您在其中插入 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
}

【问题讨论】:

    标签: perl text scripting lowercase capitalization


    【解决方案1】:

    Nathan Fellman 和 mobrule 都提出了一种常见做法:Normalization

    在执行作为程序或子程序主要目标的实际计算之前,处理数据以使其符合预期的内容和结构规范通常更简单。

    马尔可夫链程序很有趣,所以我决定尝试一下。

    这是一个允许您控制马尔可夫链中层数的版本。通过更改$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;
    }
    

    另一个潜伏的大问题是我使用- 作为非字字符。如果您想包含连字符作为标点符号,则需要在第 8 行更改 NONWORD 常量定义。只需选择永远不会是单词的东西。

    【讨论】:

    • 感谢 daotoad 的大力帮助。我不确定我是否完全理解它(还),但我相信这是正确的答案!还有一个问题 - 这不会小写(标准化?)所有输出 - 还是这样?我现在正在测试它!
    • 实际上它将所有输入大写(参见 normalize_line),然后去掉所有非字母字符(参见 normalize_word)。如果您想要小写输出,只需更改规范化函数。如果您使用小写字母,则需要调整 normalize word 中的正则表达式以接受小写字符。
    • @daotoad:您能否在上面经过大量修改的示例中“保留大小写并将标点符号分类为单词”?我已经将您的答案标记为正确-因此对您自己没有真正的好处:P-但这可以节省我目前不必付出的工作时间!再次感谢 - 非常有见地。
    【解决方案2】:

    在处理之前将所有输入转换为小写?

    the lc function

    【讨论】:

    • 因此生成的文本也将全部小写,不是吗?例如,“Mr. Feldman”这两个词也需要在输出中大写。
    • @darkAsPitch - 确实如此。您需要在处理后进行资本重组。或者,您可以选择性地规范化,例如,不要规范化以大写字母开头的句子中间的单词,并且如果您可以将句子开头的单词识别为专有名词,则不要规范化它等。
    • 问题是,如果你在状态表中的单词之间画一个等价词,比如'One'和'one',输出格式化程序如何知道如何大写输出?一种选择是不区分大小写,将标点符号分类为单词。所以,“他吃了奶酪。食物很好吃。”将有 9 个不同的单词,只有 '.'重复。在确定什么是单词时,您有很大的灵活性。它可以是单个字母,甚至是完整的句子。这真的取决于你想如何使用你的输出。
    • @daotoad:“不区分大小写,将标点符号归类为单词”——我相信这是我一直在寻找的真正答案!然后,您可以轻松地将句首和已知名称大写在另一个脚本中。这是这里的总体思路吗?我对 Perl 很陌生。现在,我如何标记评论的正确答案? :P
    【解决方案3】:

    我认为最好的办法是在输入单词后立即将其小写(或大写):

    while (<>)
    {                                        # read each line of input
        lc; # convert $_ to lowercase
        # etc.
    }
    

    【讨论】:

    • 因此生成的文本也将全部小写,不是吗?例如,“Mr. Feldman”这两个词也需要在输出中大写。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-30
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多