perl的学习:将分句脚本split-sentences.perl转为python脚本

  初识perl,只为完成分句脚本的转换。因此本文具有极强的目的性,perl的很多好用功能就不研究了,主要内容围绕分句脚本展开,部分基础知识就不再赘述。
  2.背景简介:仓库是一个将自然语言处理机器翻译系统Moses的分句功能脚本从perl转为python的程序源码。
  3.perl分句脚本地址:https://github.com/moses-smt/mosesdecoder/blob/RELEASE-2.1.1/scripts/ems/support/split-sentences.perl ,在我的仓库相应路径也有该文件,应该是版本不一致,可以参考官方最新版本。
  印象里大多数脚本文件都是顺序执行,方法、变量的命名定义在不同的文件里有不同的规则。早些年的相对通用规则是,如果需要调用方法或者变量,需要再调用前对方法、变量进行定义声明。因此对于脚本文件可以先通读一遍,理出大概结构后,再进行功能研究。脚本通读之后,大致将脚本分为四个部分。1、变量的定义声明;2、参数相关处理;3、脚本的逻辑处理;4.分句实际方法。
  一、变量。perl的变量类型有三类:标量,数组和哈希
  标量变量以$开头,概念很好理解,对标其他语言的数字字符串。标量可以是个完整的网页,但是没有看到相关的例子,知道即可不必过于关注。另外单引号和双引号的使用需要注意。单引号常用于原样输出和多行文本,即在单引号中输出的是变量名而非变量值;单引号下的多行文本可以理解为所见即所得。标量中的多行文本也可以用document语法来输出,在大多数高级语言或常见脚本语言中类似的输出相对少见,不必过分关注。
  标量的运算,常见的是拼接(.)。加减乘除需要两个变量是数字才能正确计算结果,如果是数字和字符串加和,就可以发现字符串被当作0来处理,但并不会报语法错误,这点和python是不同的(python抛出类型异常)。
  数组变量以@开头,常见的定义方式直接以括号和逗号(,)来定义,也可以用qw//来定义(将元素以空格隔开写在斜杠内)。访问元素时以$[]来进行操作。注意数组的长度是数组物理大小,不是数组内的元素个数,因此在使用数组索引的时候,有可能遇到空元素。
my @array=(1,2,3);
$array[50]=4; #此时数组长度为51,但数组中只有4个元素(index:0,1,2,50)。

  数组其他的功能如添加删除元素,切割、替换数组,将字符串转为数组,将数组拼接成字符串,排序,合并,选择等操作不再解释,相关教程很详细。

  哈希变量以%开头,定义的方式有多种,常见以列表来定义(列表中的元素,第一个为key,第二个为value,以此类推)。也可以在列表中以=>的方式自行指定key=>value。对标其他语言中的字典类型便于理解。常用操作有读取、赋值,增删元素,迭代遍历等操作,不再赘述。
  二、参数。参数主要针对脚本的参数@ARGV。
  我们在调用脚本的时候,会把调用的命令行里的参数统统放到@ARGV数组里。
  在文件的开头我们指定了STDIN,STDOUT,STDERR三个标准输入/标准输出/错误信息参数的编码方式。
  三、逻辑处理
  perl既是简洁的也是冗余的,简洁在于很多语法都可以省略,省略到有其他开发经验的perl初学者一脸懵逼;冗余在于很多“理所应当”的常见操作都会有专门的关键词来处理。几乎接触过的所有编程语言的核心逻辑处理只有三类,判断、循环、跳转
  
  跳转,这个操作现在大多数语言的学习中都不推荐,甚至逐渐移除这个操作。
  
  判断,perl中总体有三种。switch case else组合。if elsif else组合。unless elsif else 组合。
  注意,数字 0, 字符串 '0' 、 "" , 空 list() , 和 undef 为 false ,其他值均为 true。 true 前面使用 ! 或 not则返回 false 。
  1.switch case else 组合。switch判断一个变量,根据变量值产生case分支,变量值可以等于某一具体值,可以在一个数组或者哈希中,比较灵活。else分支就是在无法匹配case分支时生效。
  2.if elsif else组合。根据条件,在各判断为真时,进入分支执行方法体。
  3.unless elsif else组合。根据条件,在unless判断为假、其他判断为真时,进入各分支执行方法体。说白了 unless 就是 lf not。
  
  循环,perl中多了一个until循环。其他的循环类型如 for,foreach,while,do while都是和其他语言类似的。循环中的控制语句有next,last,continue,redo,goto这五种。以下均不讨论标签块的使用,使用标签块在某种意义上是跳转,跳转时超纲范围,知道即可。
  1.until和while的用法时一样,只是当untile的判断条件为假时执行循环体,而while则是判断条件为真执行循环体体。
  2.next,将结束本次循环开始下一次循环。对标其他语言(如C#,java)中的continute。
  3.last,结束当前循环,不是结束本次循环。因为有嵌套循环的存在,一定要看清last结束的是哪个循环。对标其他语言(如C#,java)中的break。  
  4.continue,当判断条件成立执行循环体之后,执行continue后的内容。注意,如果循环体内有next,next执行之后continue也会执行。如果循环体内有last,last执行之后不执行continue。
  5.redo,直接将控制转到循环体第一行,redo之后的语句不执行。如果有continue,在redo将控制转到循环体第一行之前不执行continue。与next存在差别,请仔细区分。
 
  四、分句脚本的详解。
  前文以不涉及分句脚本的方式将用到perl基本语法简要介绍了一下。下面针对分句脚本进行学习。
  在while(@ARGV)之前,脚本先后指定了标准输入输出的编码格式,然后定义了常用的变量。如前缀数组,如语言类型等。
while (@ARGV) {
$_ = shift;  #是 $_=shift(@ARGV); 的简写,将数组的第一个元素移除并赋值个特殊变量$_。$_是默认输入和模式匹配内容,特殊变量最常用的操作就是省略,这就导致了代码对于初学者不易读。
/^-l$/ && ($language = shift, next); # 也是简写,相对完整的写法如下
/^-q$/ && ($QUIET = 1, next); #循环体内其他代码也是类似的简写。
/^-h$/ && ($HELP = 1, next);
}
/^-l$/ && ($language = shift, next); 相对完整的代码如下:

if ($_m/^-l&/ ){ # 如果$_匹配成功

$language = shift(@ARGV); # 从@ARGV中再取出一个元素赋值给$language
next;                            # 结束本次循环,开始下次循环
}

while(@ARGV){}   是一个标准循环。在调用脚本的时候将参数定义为数组,传递给@ARGV。

if (!(-e $prefixfile)) {}    -e 文件名 用于判断文件是否存在。
my $text = "";
while(<STDIN>) {                       # 对标准输入进行循环,如果是<>,则是对文件输入进行循环
chop;                                  # 早期版本是 chomp; 完整的代码应该是$_=<STDIN>;chop($_); 对输入内容去除最后一个字符或数组中每个元素的最后一个字符,通常用于去掉换行符。
if (/^<.+>$/ || /^\s*$/) {             # 对$_进行正则匹配 ,如果是特殊变量$_,很多地方可以省略。匹配模式m,也可以省略。
#time to process this block, we've hit a blank or <p>
&do_it_for($text,$_);                  #当输入的内容是空行或者<p>,就开始调用方法处理已输入内容
print "<P>\n" if (/^\s*$/ && $text); ##if we have text followed by <P>
$text = "";
}
else {
#append the text, with a space
$text .= $_. " ";                      # . 用空格连接字符串
}
}
sub do_it_for {                        #定义方法
my($text,$markup) = @_;                # @_变量用来传递变量
print &preprocess($text) if $text;     # 如果$text 不为空,则调用preprocess函数。
print "$markup\n" if ($markup =~ /^<.+>$/);
#chop($text);
}

 

关于preprocess函数,需要深度了解正则表达式,其他的基础语法上文已经基本涵盖。
 
$text =~ s/ +/ /g;    #将多个空格替换为单个,全局模式。 s是正则中的替换模式,=~s/// eg: 变量=~s/正则表达式/替换后内容/ 是将变量按正则替换为指定表达,并赋值给变量。
$text =~ s/([?!]) +([\'\"\(\[\¿\¡\p{IsPi}]*[\p{IsUpper}])/$1\n$2/g;        # $1\n$2 是正则的分组,由于替换后将原文分为两组,对正则的解读就需要注意分组来解析。
 
关于正则的内容,经过这个脚本的学习,再不敢言说自己懂正则表达式了。再赘述两句关于正则的内容,块和分组
块的使用增加了正则对unicode的支持,使用的方法如: \p{Upper} 匹配大写字母,等效于[A-Z] 。早期的perl分句脚本中有对其他语言块的专门匹配,语言块请参考资料1的Unicode块和分类章节。 
分组,应该算是正则表达式的高级内容。以左小括号出现顺序来确定排序。如:((A)(B(C)))
分组0表示表达式自身匹配到的内容。
分组1:((A)(B(C)))
分组2:(A)
分组3:(B(C))
分组4:(C)
分组还涉及到不捕获和断言,有兴趣的自己看吧,相关资料比较多,没用多少不敢乱说。
 
 

posted @ 2020-10-29 10:07  单亚林  阅读(389)  评论(0编辑  收藏  举报