perl操作二維陣列

miguelmin發表於2009-01-15

NAME
perllol - 運算元組的陣列(二維陣列)

說明
宣告和訪問陣列的陣列
建立一個陣列的陣列(有時也可以叫“列表的列表”,不過不太準確)真是再簡單也不過了。它相當容易理解,並且本文中出現的每個例子都有可能在實際應用中出現。

陣列的陣列就是一個普通的陣列(@AoA),不過可以接受兩個下標("$AoA[3][2])。

[@more@]下面先定義一個這樣的陣列:

# 一個包含有“指向陣列的引用”的陣列
@AoA = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
);
print $AoA[2][2];

你可能已經注意到,外面的括號是圓括號,這是因為我們想要給陣列賦值,所以需要圓括號。如果你*不*希望這裡是 @AoA,而是一個指向它的引用,那麼就得這樣:

# 一個指向“包含有陣列引用的陣列”的引用
$ref_to_AoA = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "elroy", "judy", ],
];

print $ref_to_AoA->[2][2];

注意外面的括號現在變成了方括號,並且我們的訪問語法也有所改變。這時因為
和 C 不同,在 Perl 中你不能自由地交換陣列和引用(在 C 中,陣列和指標在
很多地方可以互相代替使用)。$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 後面的那個箭頭不能省略,因為省略
了就沒法知道 $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);
}
想要訪問的某個元素是不是存在是無關緊要的:因為如果不存在那麼 Perl 會給你自動建立!新建立的元素的值是 "undef"。

如果你想新增到一行的末尾,你可以這麼做:

# 新增新列到已存在的行
push @{ $AoA[0] }, "wilma", "betty";

注意我*沒有*這麼寫:

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

事實上,上面這句根本就沒法透過編譯!為什麼?因為 push() 的第一個引數必須是一個真實的陣列,不能是引用。

訪問和列印

現在是列印二維陣列的時候了。那麼怎麼列印?很簡單,如果你只想列印一個元 素,那麼就這麼來一下:

print $AoA[0][0];

如果你想列印整個陣列,那你可不能這樣:
print @AoA; # 錯誤!

因為你這麼做只能得到一列引用,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 ] ];
}

其實用 map 函式可以更加簡練:

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

雖然你的經理也許會抱怨這種難以理解的程式碼可能會帶來安全隱患,
然而這種觀點還是頗有爭議的(興許還可以更加安全也說不定 ^_^)。

換了是我,我會把它們放進一個函式中實現:
@newAoA = splice_2D( @AoA, 4 => 8, 7 => 12 );
sub splice_2D {
my $lrr = shift; # 指向二維陣列的引用
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 <>

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/16723161/viewspace-1016127/,如需轉載,請註明出處,否則將追究法律責任。

相關文章