Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1016555
  • 博文数量: 128
  • 博客积分: 10012
  • 博客等级: 上将
  • 技术积分: 2050
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-15 17:49
文章分类

全部博文(128)

文章存档

2011年(16)

2009年(57)

2008年(55)

分类:

2009-03-13 13:48:24


     在前面讲的数组变量中,可以通过下标访问其中的元素。例如,下列语句访问数组@array的第三个元素:
     $scalar = $array[2];
     虽然数组很有用,但它们有一个显著缺陷,即很难记住哪个元素存贮的什么内容。假如我们来写一个程序计算某文件中首字母大写的单词出现的次数,用数组来实现就比较困难,程序代码如下:
1 : #!/usr/local/bin/perl
2 :
3 : while ($inputline = ) {
4 : while ($inputline =~ /\b[A-Z]\S+/g) {
5 : $word = $&;
6 : $word =~ s/[;.,:-]$//; # remove punctuation
7 : for ($count = 1; $count <= @wordlist;
8 : $count++) {
9 : $found = 0;
10: if ($wordlist[$count-1] eq $word) {
11: $found = 1;
12: $wordcount[$count-1] += 1;
13: last;
14: }
15: }
16: if ($found == 0) {
17: $oldlength = @wordlist;
18: $wordlist[$oldlength] = $word;
19: $wordcount[$oldlength] = 1;
20: }
21: }
22: }
23: print ("Capitalized words and number of occurrences:\n");
24: for ($count = 1; $count <= @wordlist; $count++) {
25: print ("$wordlist[$count-1]: $wordcount[$count-1]\n");
26: }
     运行结果如下:
Here is a line of Input.
This Input contains some Capitalized words.
^D
Capitalized words and number of occurrences:
Here: 1
Input: 2
This: 1
Capitalized: 1
     这个程序每次从标准输入文件读一行文字,第四行起的循环匹配每行中首字母大写的单词,每找到一个循环一次,赋给简单变量$word。在第六行中去掉标点 后,查看该单词是否曾出现过,7~15行中在@wordlist中挨个元素做此检查,如果某个元素与$word相等,@wordcount中相应的元素就 增加一个数。如果没有出现过,即@wordlist中没有元素与$word相等,16~20行给@wordlist和@wordcount增加一个新元 素。

     正如你所看到的,使用数组元素产生了一些问题。首先,@wordlist中哪个元素对应着哪个单词并不明显;更糟的是,每读进一个新单词,程序必须检查整个列表才能知道该单词是否曾经出现过,当列表变得较大时,这是很耗费时间的。
     这些问题产生的原因是数组元素通过数字下标访问,为了解决这类问题,Perl定义了另一种数组,可以用任意简单变量值来访问其元素,这种数组叫做关联数组,也叫哈希表。
     为了区分关联数组变量与普通的数组变量,Perl使用%作为其首字符,而数组变量以@打头。与其它变量名一样,%后的第一个字符必须为字母,后续字符可以为字母、数字或下划线。

     关联数组的下标可以为任何简单/标量值,访问单个元素时以$符号打头,下标用大括号围起来。例如:
$fruit{"bananas"}
$number{3.14159}
$integer{-7}
     简单变量也可作为下标,如:
     $fruit{$my_fruit}

     创建一个关联数组元素最简单的方法是赋值,如语句$fruit{"bananas"} = 1; 把1赋给关联数组%fruit下标为bananas的元素,如果该元素不存在,则被创建,如果数组%fruit从未使用过,也被创建。
     这一特性使得关联数组很容易用于计数。下面我们用关联数组改写上面的程序,注意实现同样的功能此程序简化了许多。
1 : #!/usr/local/bin/perl
2 :
3 : while ($inputline = ) {
4 : while ($inputline =~ /\b[A-Z]\S+/g) {
5 : $word = $&;
6 : $word =~ s/[;.,:-]$//; # remove punctuation
7 : $wordlist{$word} += 1;
8 : }
9 : }
10: print ("Capitalized words and number of occurrences:\n");
11: foreach $capword (keys(%wordlist)) {
12: print ("$capword: $wordlist{$capword}\n");
13: }
     运行结果如下:
Here is a line of Input.
This Input contains some Capitalized words.
^D
Capitalized words and number of occurrences:
This: 1
Input: 2
Here: 1
Capitalized: 1
     你可以看到,这次程序简单多了,读取输入并存贮各单词数目从20行减少到了7行。
     本程序用关联数组%wordlist跟踪首字母大写的单词,下标就用单词本身,元素值为该单词出现的次数。第11行使用了内嵌函数keys()。这个函数返回关联数组的下标列表,foreach语句就用此列表循环。
     注:关联数组总是随机存贮的,因此当你用keys()访问其所有元素时,不保证元素以任何顺序出现,特别值得一提的是,它们不会以被创建的顺序出现。
     要想控制关联数组元素出现的次序,可以用sort()函数对keys()返回值进行排列,如:
foreach $capword (sort keys(%wordlist)) {
print ("$capword: $wordlist{$capword}\n");
}

     可以用单个赋值语句创建关联数组,如:
     %fruit = ("apples",17,"bananas",9,"oranges","none");
     此语句创建的关联数组含有下面三个元素:
  • 下标为apples的元素,值为17
  • 下标为bananas的元素,值为9
  • 下标为oranges的元素,值为none
  •      注:用列表给关联数组赋值时,Perl5允许使用"=>"或","来分隔下标与值,用"=>"可读性更好些,上面语句等效于:
         %fruit = ("apples"=>17,"bananas"=>9,"oranges"=>"none");

         与列表一样,也可以通过数组变量创建关联数组,当然,其元素数目应该为偶数,如:
         @fruit = ("apples",17,"bananas",9,"oranges","none");
         %fruit = @fruit;
         反之,可以把关联数组赋给数组变量,如:
         %fruit = ("grapes",11,"lemons",27);
         @fruit = %fruit;
         注意,此语句中元素次序未定义,那么数组变量@fruit可能为("grapes",11,"lemons",27)或("lemons",27,"grapes",11)。
         关联数组变量之间可以直接赋值,如:%fruit2 = %fruit1; 还可以把数组变量同时赋给一些简单变量和一个关联数组变量,如:
         ($var1, $var2, %myarray) = @list;
         此语句把@list的第一个元素赋给$var1,第二个赋给$var2,其余的赋给%myarray。
         最后,关联数组可以通过返回值为列表的内嵌函数或用户定义的子程序来创建,下例中把split()函数的返回值--一个列表--赋给一个关联数组变量。
    1: #!/usr/local/bin/perl
    2:
    3: $inputline = ;
    4: $inputline =~ s/^\s+|\s+\n$//g;
    5: %fruit = split(/\s+/, $inputline);
    6: print ("Number of bananas: $fruit{\"bananas\"}\n");
         运行结果如下:
    oranges 5 apples 7 bananas 11 cherries 6
    Number of bananas: 11

         增加元素已经讲过,可以通过给一个未出现过的元素赋值来向关联数组中增加新元素,如$fruit{"lime"} = 1;创建下标为lime、值为1的新元素。
         删除元素的方法是用内嵌函数delete,如欲删除上述元素,则:
         delete ($fruit{"lime"});
    注意:
    1、一定要使用delete函数来删除关联数组的元素,这是唯一的方法。
    2、一定不要对关联数组使用内嵌函数push、pop、shift及splice,因为其元素位置是随机的。

         上面已经提到,keys()函数返回关联数组下标的列表,如:
    %fruit = ("apples", 9,
    "bananas", 23,
    "cherries", 11);
    @fruitsubs = keys(%fruits);
         这里,@fruitsubs被赋给apples、bananas、cherries构成的列表,再次提请注意,此列表没有次序,若想按字母顺序排列,可使用sort()函数。
         @fruitindexes = sort keys(%fruits);
         这样结果为("apples","bananas","cherries")。类似的,内嵌函数values()返回关联数组值的列表,如:
    %fruit = ("apples", 9,
    "bananas", 23,
    "cherries", 11);
    @fruitvalues = values(%fruits);
         这里,@fruitvalues可能的结果为(9,23.11),次序可能不同。

         前面已经出现过利用keys()函数的foreach循环语句,这种循环效率比较低,因为每返回一个下标,还得再去寻找其值,如:
    foreach $holder (keys(%records)){
    $record = $records{$holder};
    }
         Perl提供一种更有效的循环方式,使用内嵌函数each(),如:
    %records = ("Maris", 61, "Aaron", 755, "Young", 511);
    while (($holder, $record) = each(%records)) {
    # stuff goes here
    }
         each()函数每次返回一个双元素的列表,其第一个元素为下标,第二个元素为相应的值,最后返回一个空列表。
         注意:千万不要在each()循环中添加或删除元素,否则会产生不可预料的后果。

         用关联数组可以模拟在其它高级语言中常见的多种数据结构,本节讲述如何用之实现:链表、结构和树。

  •      许多编程语言可以定义结构(structure),即一组数据的集合。结构中的每个元素有其自己的名字,并通过该名字来访问。
         Perl不直接提供结构这种数据结构,但可以用关联数组来模拟。例如模拟C语言中如下的结构:
    struce{
    int field1;
    int field2;
    int field3; }mystructvar;
         我们要做的是定义一个含有三个元素的关联数组,下标分别为field1、field2、field3,如:
    %mystructvar = ("field1" , "" ,
    "field2" , "" ,
    "field3" , "" ,);
         像上面C语言的定义一样,这个关联数组%mystrctvar有三个元素,下标分别为field1、field2、field3,各元素初始值均为空串。对各元素的访问和赋值通过指定下标来进行,如:
         $mystructvar{"field1"} = 17;

         另一个经常使用的数据结构是树。树与链表类似,但每个节点指向的元素多于一个。最简单的树是二叉树,每个节点指向另外两个元素,称为左子节点和右子节点(或称孩子),每个子节点又指向两个孙子节点,依此类推。
         注:此处所说的树像上述链表一样是单向的,每个节点指向其子节点,但子节点并不指向父节点。
         树的概念可以如下描述:
  • 因为每个子节点均为一个树,所以左/右子节点也称为左/右子树。(有时称左/右分支)
  • 第一个节点(不是任何节点的子节点的节点)称为树的根。
  • 没有孩子(子节点)的节点称为叶节点。
  •      有多种使用关联数组实现树结构的方法,最好的一种应该是:给子节点分别加上left和right以访问之。例如,alphaleft和alpharight指向alpha的左右子节点。下面是用此方法创建二叉树并遍历的例程:
    1 : #!/usr/local/bin/perl
    2 :
    3 : $rootname = "parent";
    4 : %tree = ("parentleft", "child1",
    5 : "parentright", "child2",
    6 : "child1left", "grandchild1",
    7 : "child1right", "grandchild2",
    8 : "child2left", "grandchild3",
    9 : "child2right", "grandchild4");
    10: # traverse tree, printing its elements
    11: &print_tree($rootname);
    12:
    13: sub print_tree {
    14: local ($nodename) = @_;
    15: local ($leftchildname, $rightchildname);
    16:
    17: $leftchildname = $nodename . "left";
    18: $rightchildname = $nodename . "right";
    19: if ($tree{$leftchildname} ne "") {
    20: &print_tree($tree{$leftchildname});
    21: }
    22: print ("$nodename\n");
    23: if ($tree{$rightchildname} ne "") {
    24: &print_tree($tree{$rightchildname});
    25: }
    26: }
         结果输出如下:
    grandchild1
    child1
    grandchild2
    parent
    grandchild3
    child2
    grandchild4
         该程序创建的二叉树如下图:

     

         注意函数print_tree()以次序“左子树、节点、右子树”来输出各节点的名字,这种遍历次序称为“左序遍历”。如果把第22行移到19行前,先输 出节点明,再输出左子树、右子树,则为“中序遍历”,如果把第22行移到25行后,输出次序为左子树、右子树、节点,则为“右序遍历”。
         可以用同样的方法,即连接字符串构成下标,来创建其它的数据结构,如数据库等。
    阅读(1000) | 评论(0) | 转发(0) |
    给主人留下些什么吧!~~