今天阅读《Perl Hacks》这本书,看到Ackermann函数和Memoize模块的介绍, 才想起以前在学习递归和算法的时候曾经见过这个Ackermann, 不过那时怎么也没有想到这个函数居然有如此强大的“威力”——发散得极其迅速, 以至于参数稍大一点就不可能算出它的精确值。于是它经常被用作电脑的benchmark……
Ackermann函数是数学史上和计算机史上的一个经典递归函数,它的表达式并不复杂, 但却几乎无法计算。其表达式如下:
A(m,n) = n+1 when m=0;
= A(m-1,1) when n=0;
= A(m-1,A(m,n-1)) otherwise.
用Perl语言写成代码如下:
sub ackermann {
my ( $m, $n ) = @_;
return $n + 1 if $m == 0;
return ackermann( $m - 1, 1 ) if $n == 0;
return ackermann( $m - 1, ackermann( $m, $n - 1 ) );
}
计算这个函数的值需要非常长的时间,因为该函数在 m 增加时发散得异常迅速…… 至于如何迅速大家可自行查资料(这个函数中文似乎叫做阿克曼函数)。
阅读全文 »书写代码时要保持良好的代码风格,缩进、注释、空行等东西一个都不能错。 不过这一点很难做到,即使是一个经验丰富的程序员也难免弄错, 更别说新手了。另外,有时阅读别人写得面条代码也是异常头疼。 不过 Perl 提供了 Perl::Tidy 这个工具可以直接将乱七八糟的 Perl代码整理成格式完美的代码。
我用的是 Windows 下的 ActivePerl 5.8, 因此首先要通过 ppm 安装 Perl::Tidy 软件包。安装方法很简单,启动 ppm 之后 s Perl-Tidy 然后 install 就可以了,不再细说。
Linux下的同学们可以直接去 CPAN 下载源代码进行编译。
安装完成后会生成一个 perltidy 命令。Windows版也有这个命令(位于 C:\Perl\bin\perltidy.bat), 当然前提是你将 C:\Perl\bin 加到了你的 PATH 中。
对某个Perl源代码只要运行下面的命令即可:
perltidy foo.pl
整理好格式的代码会保存为 foo.pl.tdy。
我常用的编辑器是 gvim,自然想到如何将这个功能集成到 gVim 中。 方法很简单,建立 perl.vim,内容如下:
" 调用perltidy整理源代码 nnoremap ,pt :%!perltidy<CR> vnoremap ,pt :!perltidy<CR>
然后将 perl.vim 放到 C:\Program Files\Vim\vimfiles\ftplugin 目录下即可。 以后打开perl源代码之后,直接用 ,pt 快捷键或者用 Shift-V 选择之后再用 ,pt 即可整理正在编辑的代码的格式。
2008-2-14更新
为perltidy加上 -t -nola 参数之后,即可使用TAB缩进而不是空格缩进。perl.vim如下:
" 调用perltidy整理源代码 nnoremap ,pt :%!perltidy -t -nola<CR> vnoremap ,pt :!perltidy -t -nola<CR>
Perl的XML::DOM功能很强大,利用它可以轻易地分析XML文档,也可以建立XML文档。
常用的类包括:
- XML::DOM::Node:所有类的基类,表示一个XML节点。
- XML::DOM::Parser:XML解析器,将XML字符串解析成XML::DOM::Document对象。
- XML::DOM::Document:指向XML文档的根节点。
- XML::DOM::Element:指向一个元素,通常由XML::DOM::Document->createElement元素生成。
其他的类可以查看CPAN上关于 XML::DOM 的说明。
建立XML文档的通常做法是:首先通过 XML::DOM::Parser生成一个Document对象, 再通过这个Document对象的 createElement、createTextNode 等方法生成各个节点, 最后通过 XML::DOM::Node->appendChild 方法将节点添加到Document对象中, 最后再通过 toString 方法将 Document 转换为字符串输出。
下面是建立XML文档的一个例子:
#!/usr/bin/perl
use XML::DOM;
use XML::Twig;
# 创建XML的最外层元素
my $xml = "<MyXML/>";
# 通过XML::DOM::Parser解析外层元素,创建基础XML文档
# XML::DOM::Parser::parse函数的返回值为 XML::DOM::Document 类
my $parser = new XML::DOM::Parser;
my $dom = $parser->parse($xml);
# 生成Book节点
# createElement的返回值为 XML::DOM::Element 类
# XML::DOM::Element 的父类是 XML::DOM::Node 类
my $book = $dom->createElement("Book");
# 设置节点属性
$book->setAttribute("title", "My First Book");
$book->setAttribute("author", "charlee");
# 生成Chapter节点
my $chapter = $dom->createElement("Chapter");
$chapter->setAttribute("id", "1");
# 生成一个文本节点
my $title = $dom->createTextNode("My First Chapter");
$chapter->appendChild($title);
# 添加Chapter到Book
$book->appendChild($chapter);
# 添加Book到XML文档
$dom->getDocumentElement->appendChild($book);
# 利用XML::Twig整理格式
my $twig = new XML::Twig;
$twig->set_indent(" "x4);
$twig->parse($dom->toString);
$twig->set_pretty_print("indented");
# 输出
print $twig->sprint;
最后一段使用XML::Twig的程序只是为了整理XML输出结果的格式, 如果不需要整理格式,不调用XML::Twig,直接使用$dom->toString也可以。
总觉得在Perl里面读取文本文件应该用个循环:
while (<FH>) { print; }
或者是用数组:
@data = <FH>;
今天看代码时看到这样一段,真有种大开眼界的感觉。
$data = do { local $/; <FH>; };
查了查资料才明白:do表示执行后面括号内的语句,$/变量是读取文本文件时的行分隔符,默认是换行(\n)。 那么这段代码的意思是,声明 local $/,使得在 {} 内 $/ 的值变成空值,那么从<FH>读取时就不会再以 换行为分隔符,而是一次性将所有内容全部读出来。最后再用 do 将返回值赋给$data。
ActivePerl是Windows下最常用的Perl版本,它使用一个名为ppm的工具来管理模块。 安装Perl模块最简单的方法就是利用 ppm,search之后再install。但由于某些原因(比如ppm不能上网)导致不能直接 使用ppm安装模块时,可以利用下面的方法。
用浏览器打开ActiveState的Perl模块发布站, 然后选择相应的版本(一般是选择8xx的Windows目录)。注意模块列表页面比较大,须耐心等待。 模块列表打开后选择自己需要的zip包下载,例如我下载了 DBD-Oracle-1.17.zip。
下载之后将其解压到C:\下(不一定是C盘,但一定要解压到根目录下),出现两个文件:DBD-Oracle-1.17.tar.gz和 DBD-Oracle-1.17.ppd。然后打开命令行,切换路径到到 C:\,然后输入 ppm-shell 启动 ppm,输入以下命令即可安装。
install C:\DBD-Oracle-1.17.ppd
最近在用 perl 写一个Linux下的多进程守护进程,因此研究了一下Linux下的进程相关的知识。 现将心得总结一下。主要是关于进程创建和回收。
阅读全文 »前面这篇文章简单地阐述了用Perl创建守护进程(daemon)的方法。 实际上,创建一个多进程的守护进程需要注意很多事情。
阅读全文 »调试 Perl 程序时使用最频繁的就是 print 语句了,不过它只能输出普通变量的内容, 而不能输出数组和哈希。使用 Data::Dumper 模块则可以输出数组和哈希等复杂变量。
#!/usr/bin/perl
use Data::Dumper;
my $hashref = { name=>'charlee', age=>25, gender=>'male', interest=>[ 'computer', 'movie', 'cooking' ] };
print Data::Dumper::Dumper($hashref);
注意Dumper函数的参数为引用。输出结果为:
$VAR1 = {
'name' => 'charlee',
'interest' => [
'computer',
'movie',
'cooking'
],
'age' => 25,
'gender' => 'male'
};
Devel::SmallProf 是个很好用的模块,可以方便地测量出代码每一行的执行时间,以便进一步优化。
例如以下程序,文件名为 prof_sample.pl。
#!/usr/bin/perl
my $str = "0";
for (my $i = 0; $i < 100; $i++) {
$str =~ s/\d+/($&+1)/e;
print $str."\n";
}
该程序的功能是输出整数 1 到 100。当然实际写程序时可不要用这么低效率的方法。 安装 Devel::SmallProf 之后我们来测量一下它每一行代码的执行时间。
perl -d:SmallProf prof_sample.pl
执行之后会在当前目录下生成一个 smallprof.out 文件,其内容如下:
================ SmallProf version 1.15 ================
Profile of prof_sample.pl Page 1
=================================================================
count wall tm cpu time line
0 0.000000 0.000000 1:#!/usr/bin/perl
0 0.000000 0.000000 2:
1 0.000006 0.000000 3:my $str = "0";
101 0.006418 0.010000 4:for (my $i = 0; $i < 100; $i++) {
200 0.002581 0.000000 5: $str =~ s/\d+/($&+1)/e;
100 0.001509 0.000000 6: print $str."\n";
1 0.000003 0.000000 7:}
前三列的数字分别为执行次数、消耗时间、消耗CPU时间。
如果你的程序使用 use 语句引用了其他模块,那么所有被引用的程序都将被分析,生成一个长长的报告。这时可以使用下面的命令来迅速找到耗时最长的命令。
sort -k 2nr,2 smallprof.out | less
今天客户发过来一个bug报告,说发送邮件时内容中书写はぁ~,收信时会显示成はち。 懂日文的朋友大概能明白,感叹词はぁ~怎么就变成了蜜蜂はち呢?
后来找到了原因。我们的邮件系统是用 Perl 写成的。其中有一个空格删除功能, 就是在显示时将邮件中的全角空格全部删除,其实现方法如下:
$body =~ s/ //g;
看起来似乎没有问题,但就是这条语句将はぁ~变成了はち。日文 euc-jp 编码下, 这几个字符串的编码如下:
| 字符串 | 编码 |
| はぁ~ | A4CF A4A1 A1C1 |
| はち | A4CF A4C1 |
| 全角空格 | A1A1 |
ぁ的后半个字符和~的前半个字符恰好都是 A1,所以就被删除了,于是就出现了有意思的乱码现象。 修改方法也比较简单,只要正确识别出每个汉字的起止位置即可。
my $twoBytes = '[\x8E\xA1-\xFE][\xA1-\xFE]';
my $threeBytes = '\x8F[\xA1-\xFE][\xA1-\xFE]';
while($body =~ s/\G(($twoBytes|$threeBytes|[\x00-\x7F])+?) /$1/g){}; # 识别汉字,如果汉字后面有空格则删除
$body =~ s/^ //g; # 最后删除最开始处的全角空格
