2007-10
04

今天阅读《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 增加时发散得异常迅速…… 至于如何迅速大家可自行查资料(这个函数中文似乎叫做阿克曼函数)。

阅读全文 »

2007-10
01

书写代码时要保持良好的代码风格,缩进、注释、空行等东西一个都不能错。 不过这一点很难做到,即使是一个经验丰富的程序员也难免弄错, 更别说新手了。另外,有时阅读别人写得面条代码也是异常头疼。 不过 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>


2007-09
14

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也可以。



2007-09
03

总觉得在Perl里面读取文本文件应该用个循环:

while (<FH>) { print; }

或者是用数组:

@data = <FH>;

今天看代码时看到这样一段,真有种大开眼界的感觉。

$data = do { local $/; <FH>; };

查了查资料才明白:do表示执行后面括号内的语句,$/变量是读取文本文件时的行分隔符,默认是换行(\n)。 那么这段代码的意思是,声明 local $/,使得在 {} 内 $/ 的值变成空值,那么从<FH>读取时就不会再以 换行为分隔符,而是一次性将所有内容全部读出来。最后再用 do 将返回值赋给$data。



2007-03
27

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


2006-10
14

最近在用 perl 写一个Linux下的多进程守护进程,因此研究了一下Linux下的进程相关的知识。 现将心得总结一下。主要是关于进程创建和回收。

阅读全文 »

2006-10
12

前面这篇文章简单地阐述了用Perl创建守护进程(daemon)的方法。 实际上,创建一个多进程的守护进程需要注意很多事情。

阅读全文 »

2006-10
03

调试 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'
        };


2006-10
03

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


2006-10
02

今天客户发过来一个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;        # 最后删除最开始处的全角空格