Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8671
  • 博文数量: 5
  • 博客积分: 40
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-15 16:30
文章分类

全部博文(5)

文章存档

2015年(5)

我的朋友
最近访客

分类: PERL

2015-01-07 15:55:11

对数组的数组的声明和访问

创建一个数组的数组(有时候不这么叫,而是不太准确的称为列表的列表)是非常简单的。它简单易懂,而且几乎所有用到的知识都有助于将来处理更复杂的数据结构。

数组的数组,顾名思义,就是一个数组@AoA,但是可以使用两个下标,就像$AoA[3][2]。下面是对这种数组的声明:

    # assign to our array, an array of array references
    @AoA = (
           [ "fred", "barney" ],
           [ "george", "jane", "elroy" ],
           [ "homer", "marge", "bart" ],
    );
    print $AoA[2][2];
      bart

要特别注意的是,最外面的括号是个圆括号。因为这是在给一个数组@array赋值,数组赋值要用圆括号。如果你想要的不是一个数组@AoA,而是一个数组的引用,则可以如下:

    # assign a reference to array of array references
    $ref_to_AoA = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "elroy", "judy", ],
    ];
    print $ref_to_AoA->[2][2];

注意这里最外面的括号变了,因此对数据的访问语法也变了。与C不同,在Perl里,数组和数组的引用不能自由的隐式转换。$ref_to_AoA是一个数组的引用,而@AoA是一个数组。同样的,$AoA[2]不是数组而是数组的引用。然而下列代码

    $AoA[2][2]
    $ref_to_AoA->[2][2]

怎么就能替换如下的这两行代码呢

    $AoA[2]->[2]
    $ref_to_AoA->[2]->[2]

其实呢,这是因为规则是这样定的:当且仅当解引用箭头出现在相邻的两个括号之间(不管是方括号还是花括号),箭头可以省略。但是,如果某个变量是一个引用,则该变量的第一级解引用不能这样使用,也就是$ref_to_AoA是必需要加箭头的。

 

扩展你的数据结构

对于声明一个固定的数据结构来说,上面的代码就足够了。但是如果想动态的增加新的数据元素,或者想整个的重建,该怎么办?

首先,看一下读取文件创建的情况。就是一次增加数组的一行元素。假设有一个文件,文件的每一行数据都是数组的一行元素,每个词是一个元素。如果要构造一个数组@AoA以包含全部数据,如下是正确的方法

    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

也有可能通过一个函数来导入数据

    for $i ( 1 .. 10 ) {
        $AoA[$i] = [ somefunc($i) ];
    }

或者用一个临时变量来保存这个数组

    for $i ( 1 .. 10 ) {
        @tmp = somefunc($i);
        $AoA[$i] = [ @tmp ];
    }

必需要注意的是一定要使用数组引用构造符[],因为下面的方式是非常严重的错误:

    $AoA[$i] = @tmp;

看吧,以这种形式将一个数组赋值给一个标量,其结果得到的只是将@tmp里元素的个数,而这个结果很有可能不是你想要的。

如果程序中用了use strict,则必须增加变量的声明:

    use strict;
    my(@AoA, @tmp);
    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

当然,临时变量是根本不需要的

    while (<>) {
        push @AoA, [ split ];
    }

也可以不用push,如果知道要把引用赋值到何处,可以直接进行赋值

    my (@AoA, $i, $line);
    for $i ( 0 .. 10 ) {
        $line = <>;
        $AoA[$i] = [ split ' ', $line ];
    }

进一步简化为

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', <> ];
    }

某些函数可能会返回列表,在标量环境下,如果没有明确的指定,要非常小心的使用这些函数。因此,下面的写法会更加清晰

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', scalar(<>) ];
    }

如果希望得到数组的引用$ref_to_AoA,则需要如下处理

    while (<>) {
        push @$ref_to_AoA, [ split ];
    }

这样就可以为数组增加新的行了。但是怎样增加新的列呢?如果读取的是矩阵,简单赋值就可以做到

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            $AoA[$x][$y] = func($x, $y);
        }
    }
    for $x ( 3, 7, 9 ) {
        $AoA[$x][20] += func2($x);
    }

至于这些下标的元素是否已经存在,那是无关紧要的。在必要的时候,会自动创建,并将间隔的那些元素都赋值为undef。

如果只是想在某一行追加元素,需要更有趣的代码

    # add new columns to an existing row
    push @{ $AoA[0] }, "wilma", "betty";

注意,不能只是如下

    push $AoA[0], "wilma", "betty";  # WRONG!

实际上,上面这行代码甚至编译不过。为虾米?因为push的参数必须是个数组,而不能只是个数组的引用。

 

对数据的访问和输出

现在要把数据结构输出。怎么做呢?嗯,如果只是输出其中一个元素,那很简单

    print $AoA[0][0];

但是,如果要输出整个数据结构,可不能简单的像下面这样

    print @AoA;         # WRONG

因为这样做只能把那些引用给列出来,perl不会自动解引用的。因此,你需要自己实现一两个循环。下面的代码输出了整个结构体,使用的是类似shell的for循环结构来遍历第一级下标。

    for $aref ( @AoA ) {
        print "\t [ @$aref ],\n";
    }

如果你想要显示的用到下标,可以如下所示

    for $i ( 0 .. $#AoA ) {
        print "\t elt $i is [ @{$AoA[$i]} ],\n";
    }

甚至还可以像下面这样,注意内层循环

    for $i ( 0 .. $#AoA ) {
        for $j ( 0 .. $#{$AoA[$i]} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

看到了吧,似乎有些麻烦了呢。因此有时候用些临时变量会简单一些

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        for $j ( 0 .. $#{$aref} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

不过好像还是挺难看的,这样如何呢

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        $n = @$aref - 1;
        for $j ( 0 .. $n ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

片段

在一个多维数组中,如果想要得到一个片段(也就是一行的一部分),需要设置一些比较巧妙的下标。因为,借助箭头解引用,虽然可以很方便的访问单个元素,但是对片段来说就没那么简单了。(当然要记住,总可以通过循环来实现片段操作)

下面给出如何通过一个循环得到一个片段。假设@AoA与之前的定义相同。

    @part = ();
    $x = 4;
    for ($y = 7; $y < 13; $y++) {
        push @part, $AoA[$x][$y];
    }

这个循环可以用一个片段操作来替代

    @part = @{ $AoA[4] } [ 7..12 ];

不过显而易见,这种写法很难懂。

如果要一个二维片段呢,例如让$x取值4到8,$y取值7到12,该怎么办呢?简单:

    @newAoA = ();
    for ($startx = $x = 4; $x <= 8; $x++) {
        for ($starty = $y = 7; $y <= 12; $y++) {
            $newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
        }
    }

可以用片段操作来减少循环的层数

    for ($x = 4; $x <= 8; $x++) {
        push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
    }

如果熟悉Schwartzian Transforms,可以用map来实现

    @newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;

如果你的老板认为这代码太难懂(译注,这句话看不太懂,不知道怎么翻译),那也没办法。换做我,我会把它放在函数里

    @newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
    sub splice_2D {
        my $lrr = shift;        # ref to array of array refs!
        my ($x_lo, $x_hi,
            $y_lo, $y_hi) = @_;
        return map {
            [ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
        } $x_lo .. $x_hi;
            }
参考 
perldata(1), perlref(1), perldsc(1) 
 
作者 
Tom Christiansen <>

Last update: Thu Jun 4 16:16:23 MDT 1998

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