Chinaunix首页 | 论坛 | 博客
  • 博客访问: 256540
  • 博文数量: 54
  • 博客积分: 2915
  • 博客等级: 少校
  • 技术积分: 486
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-21 12:20
个人简介

这个人很懒,什么都没有留下

文章分类
文章存档

2013年(1)

2012年(6)

2011年(11)

2010年(16)

2009年(20)

我的朋友

分类:

2009-07-26 01:04:48

文件: cyk.rar
大小: 326KB
下载: 下载
   要求就是写一个句法剖析程序。我选择了cyk算法。这里做的是针对英文句子的,而且只有cyk算法作句法剖析部分,也就是说,规则库很小的,仅作测试用,结果返回所有可能的规则树。
  

#!/usr/bin/perl

use strict;
use warnings;

my %P;        #用来存储规则库

my %V;        #用来存储填表过程中所产生的每个格子里的规则

my $i;        #普通变量

my $k;        #表示句子结构的划分处

my $j;        #普通变量

my @words;        #存放将被作句法剖析的句子,数组的每个元素依次为句子里的单词

my $cache;        #普通变量

my $cache1;    #普通变量    

my $cache2;    #普通变量

my @line;         #普通数组

my $n;        #表示句子长度,即单词数,目的仅仅是为了方便书写

my $tree;    #用在子函数Tree里面,作用是:在回溯建立树的过程中存放已经遍历过的部分,为“分歧处理”服务。属于全局变量

my $p;        #表示概率,属于全局变量

my @p = "";        #存放每个树的概率值

my @tree = "";        #存放每个树的打印输出形式

my $words;        #普通变量


#将规则库里的规则存进哈希表%P中

#往后注释里说提及的规则的左(右)半部分指的是,例如规则A->B C,则左半部分是A,右半部分是B C

open FILE, "<", "pp.txt" || die "Cannot open file: $!";
while (<FILE>) {
    chomp;
    @line = split /->/;     #以符号“->” 分割每行的字符串,得到规则的左右部分以及该规则的概率

    $P{$line[1]}{$line[0]} = $line[2];    #哈希表的第一维key存放规则的右半部分,第二维存放规则的左半部分,value存放该规则的概率。之所以要将规则的右

                                #半部分放在左半部分之前,是因为,cyk算法是按自底向上的顺序遍历的,先知道规则的右半部分,然后到规则库里找相应的左半部分。

}
close(FILE);

#输入并存放将被作句法剖析的句子

print "Please input a sentence in English:\n";
$words = <STDIN>;            #在终端输入待剖析句子,要求以空格隔开每个单词

chomp($words);            #去掉句子尾部的换行符

@words = split (/ /, $words);        #将句子切成每个单词,存到数组@words

$n = @words;        #表示句子长度,即单词数


#从句子里的单词出发,找到每个词可以对应的语法,填进表格中,在表格中它们所在格子连起来是一条对角线。

for ($i = 0; $i < $n; ++ $i) {
    if (exists $P{$words[$i]}) {        #如果规则库中有这个单词,则继续后面的步骤

        foreach $cache (keys %{$P{$words[$i]}}) {        #对于每个右半部分是该单词的规则,获得其左半部分

            $V{($i + 1) * 11}{$cache}{$words[$i]} = 0;        #哈希表%V的第一维key存放的是表格中格子的下标,这里用十进制两位数表示,如果句子长度或者数的

        }                                                                    #层数超过10也应该无所谓,不同的$i和$j经过这一表达式,相当于将下标用映射到某个整数,仍然可以用来

    }                                                                        #区分格子。哈希表的第二维key存放的是规则的左半部分,第三维key存放的是规则的右半部分。之所以要这

                                                                            #样子,是因为,回溯部分是按自上向下的顺序遍历的,先知道规则的左半部分,找其合适的右半部分。哈希表

                                                                            #的value存放的应该是$k值,在这行代码中每个格子都只表示一个单词,$k没有意义,故都用0赋值

    else {        #如果规则库中没有该单词,则输出错误信息,并且结束程序

        print "error: there is some words in the sentence that not in the Rule base!\n";
        exit(0);        #结束程序

    }
}

#cyk算法自底向上遍历同时填表的过程,所操作的是表格的下三角部分

for ($j = 1; $j < $n; ++ $j) {        #这里$j表示,在剖析句子时,划分出来待处理的句子某部分的长度

    for ($i = 1; $i <= $n - $j; ++ $i) {        #这里$i表示表格里的横坐标,即待剖析句子里单词的下标,也可以理解为,剖析句子时划分出来待处理的部分的最左

                                                        #边单词的下标

        %{$V{$i * 10 + ($i + $j)}} = ();            #待填的格子里的哈希表初始化为空,该格子的下标是“i,i+j”, i+j是纵坐标,表示剖析句子时划分部分的最后边单词下标

        for ($k = $i; $k < $i + $j; ++ $k) {        # $k表示,对剖析句子时划分出来待处理的部分作再分割处理的分割位置,即距离$i的长度

        #下面,找到所有符合要求的规则填到格子"i,i+j"中。所谓符合要求,就是,找到规则库中存在的规则X->YZ,Y属于格子“i,k”中的,而Z属于格子"k+1,i+j"中的

            foreach $cache1 (keys %{$V{$i * 10 + $k}}) {        #找出格子"i,k"中的每个规则的左半部分

                foreach $cache2 (keys %{$V{($k + 1) * 10 + ($i +$j)}}) {        #找出格子"k+1,i+j"中的每个规则的左半部分

                    if (exists $P{"$cache1 $cache2"}) {        #从上述两个格子中找出$cache1和$cache2后,对于其每种可能的组合都到规则库中查找看是否存在

                        foreach $cache (keys %{$P{"$cache1 $cache2"}}) {        #由于某种语法组合BC可能有多种语法指向,即可能A->BC,也可能D->BC,故需要考

                                                                                                        #虑完所有的情况

                            $V{$i * 10 + ($i + $j)}{$cache}{"$cache1 $cache2"} = $k;        #value值为$k,$k的作用在于表明树的父节点跟子节点的位置关系,即“某

                                                                                                                            #个格子里的规则是有哪两个格子的规则得到的”

                        }
                    }
                }
            }
        }
    }
}

#回溯遍历同时建立并打印输出符合要求的树,这个过程中还要计算每个树的概率以在最后得到一棵最优树。

#注意,打印输出结果中的树是以这样的形式表示的:整体上树是横着的,左子节点在右子节点的上方并跟父节点在同一行,同一层的左右子节点是列对齐的。

if (!(exists $V{$n + 10}{"S"})) {
    print "error: there is no tree that meets the requirements!\n" ;
    exit(0);
}
foreach $cache (keys %{$V{$n + 10}}) {            #在格子“1,n”里找到根结点是S的情况继续后面的操作

    if ($cache eq "S") {
        print "\n\nThe result is: \n";
        $tree = "";        #初始化$tree为空字符串

        $p = 1;                #初始化概率$p为1

        Tree($cache, 1, $n, $tree, "", $p);        #调用子函数Tree,参数里,第一个参数是树的根节点;第二个参数是格子的横坐标;第三个参数是纵坐标;第四个

    }                                                        #参数是$tree,表示前面回溯过程中已经遍历过部分,这是一个全局变量;第五个参数是制表符"\t",随着程序每次递归

}                                                            #该参数里的"\t"的个数将相应的变化,目的是让树的每层节点对齐输出;第六个参数是概率,这个也是全局变量


#比较各个树的概率,输出最优树

$cache = 0;        #初始化为0

$j = 0;            #初始化为0

for ($i = 0; $i != @p; ++ $i) {        #这里目的是判断出哪个树的概率最大,获得该树在数组中的下标

    if ($p[$i] gt $cache) {
        $cache = $p[$i];
        $j = $i;
    }
}
print "\n\nThe best tree is : \n";        #输出最优树及其概率

print $tree[$j];
print "\np = $p[$j]\n";

#为回溯服务的子函数Tree,大致上是一个递归算法

sub Tree{
    my $cache1 = $_[0];        #接收第一个参数,即树的根结点

    my $i = $_[1];                    #接收第二个参数,即格子横坐标

    my $j = $_[2];                #接收第三个参数,即格子纵坐标

    my $temp = $_[3];            #接收第四个参数,即$tree,在每个父节点缓存此前已经遍历过的部分,协助处理分歧点的打印输出操作

    my $pp;                #局部变量,用来缓存在分歧点的概率

    my $cache2;        #普通变量

    my @temp;            #普通数组

    my $k;                    #提取哈希表%V里的value,即表征了父节点和子节点之间的位置关系的分割点

    my @cache;            #普通数组

    my $m = 0;            #用在岐义出现处,$m ==0时表示后面的操作是对分歧点的第一次操作,否则,就不是第一次操作了,那么就得先将缓存$temp打印输出,

                        #然后才继续后面的操作,这样才可以避免打印输出时,到分歧点丢失了已遍历部分的情况出现

    my $tt;                #用来接收第五个参数的,即用来保证友好输出的制表符

    if ($i == $j) {        #如果到了叶节点,则直接输出

        @temp = keys %{$V{$i * 10 + $j}{$cache1}};        #这行代码是为了将格子"i,i"跟$cache1对应的单词,结果是数组@temp只存有一个单词

        print "$cache1\t", $temp[0];                #打印输出终极节点以及其对应的单词

        $tree = $tree."$cache1\t".$temp[0];        #将该部分接到标量$tree的后面

        $p *= $P{$temp[0]}{$cache1};                    #将该规则的概率乘到该树的概率里

        if ($j == $n) {                        #如果这个终极节点对应的单词是句子的最后一个单词了,那么说明这棵树已经遍历完成,概率$p也已经计算完毕,可以输出了

            print "\np = $p\n";
            push(@p, $p);
            push(@tree, $tree);
        }
    }
    else {                #如果是非终极节点,那么就得找到其左右子树

        foreach $cache2 (keys %{$V{$i * 10 + $j}{$cache1}}) {        #这里是一个潜在的分歧点,也就是说,如果存在两种以上的情况,则产生了岐义

            $tt = $_[4];        # $tt在这里接收第五个参数,放到这里可以达到一个目的是,对于分歧的每种情况,都初始化一次$tt

            if ($m != 0) {        #如果$m != 0,说明在产生岐义的格子里,已经不是第一次操作了,于是在开始后续操作前,得先输出缓存$temp

                print "\n\n",$temp;        #打印输出缓存$temp

                $tree = $temp;            #由于$tree是全局变量,会随着调用函数里的操作改变,所以当循环回到分歧节点时,需要把$tree还原为第一次经过该节点

                                            #时所保留下来的缓存$temp                

                $p = $pp;                        #跟$tree一样的思路,将全局变量$p还原为第一次经过该节点的概率$p的值

            }
            $pp = $p;            #这行代码主要作用是,在回溯遍历过程第一次经过这里时,将概率$p的值保留下来

            $p *= $P{$cache2}{$cache1};        #计算概率,即将原来得到的$p乘以本次遇到的规则所对应的概率

            $k = $V{$i * 10 + $j}{$cache1}{$cache2};        #获取分割点$k

            @cache = split(/ /, $cache2);        #将规则右半部分以空格分割开,并将得到的两个字节点存进数组中,供后面的操作使用

            print "$cache1\t";            #将父节点打印输出

            $tree = $tree."$cache1\t";        #将遍历过的该父节点接到$tree的后面

            $tt = $tt."\t";                    #制表符操作,层数也高,那么$tt里的"\t"个数就应该越多

            Tree($cache[0], $i, $k, $tree, $tt, $p);        #对左子树递归调用本函数

            print "\n$tt";                    #制表符操作,目的是为了让树的输出形式比较友好

            $tree = $tree."\n$tt";         #依然要把前面打印输出过的部分接到$tree后,因为不管前面打印了什么,都是树的一部分

            Tree($cache[1], $k + 1, $j, $tree, $tt, $p);        #对右子树递归调用本函数

            if ($cache1 eq "S") {        #为了打印输出结果好看些

                print "\n";
            }
            $m = 1;        #在分歧点已经处理过一次后,将$m设置为不等于0

        }
    }
}

阅读(5079) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~