perlfaq4 - 資料處理
版本 5.20210520
本節的常見問答解答了與處理數字、日期、字串、陣列、雜湊和各種資料問題相關的問題。
有關詳細說明,請參閱 David Goldberg 的「每個電腦科學家都應該知道的浮點數運算」(http://web.cse.msu.edu/~cse320/Documents/FloatingPoint.pdf)。
在內部,電腦使用二進位表示浮點數。數位(以二的次方為單位)電腦無法精確儲存所有數字。某些實數在處理過程中會失去精度。這是電腦儲存數字的方式所造成的問題,且會影響所有電腦語言,不只是 Perl。
perlnumber 顯示數字表示和轉換的詳細資料。
若要限制數字的小數位數,可以使用 printf
或 sprintf
函數。有關更多詳細資料,請參閱 perlop 中的「浮點數運算」。
printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
您的 int()
很可能運作良好。數字並非您想像的那樣。
首先,請參閱「為什麼我得到長小數(例如 19.9499999999999),而不是我應該得到的數字(例如 19.95)?」的解答。
例如,這個
print int(0.6/0.2-2), "\n";
在大部分電腦中會印出 0,而不是 1,因為即使是像 0.6 和 0.2 這麼簡單的數字,也無法用浮點數精確呈現。你認為上述的「三個」實際上更像是 2.9999999999999995559。
(由 brian d foy 貢獻)
你可能試圖將字串轉換為數字,而 Perl 只會將其轉換為十進制數字。當 Perl 將字串轉換為數字時,它會忽略前導空格和零,然後假設其餘數字為 10 進位
my $string = '0644';
print $string + 0; # prints 644
print $string + 44; # prints 688, certainly not octal!
此問題通常涉及 Perl 內建函式,其名稱與 Unix 指令相同,而 Unix 指令將八進制數字用作指令列中的引數。在此範例中,指令列中的 chmod
知道其第一個引數是八進制,因為這是它的功能
%prompt> chmod 644 file
如果你想在 Perl 中使用相同的字面數字 (644),你必須告訴 Perl 將它們視為八進制數字,方法是使用 0
為數字加上前綴,或使用 oct
chmod( 0644, $filename ); # right, has leading zero
chmod( oct(644), $filename ); # also correct
當你從 Perl 認為是字串的項目(例如 @ARGV
中的指令列引數)取得數字時,問題就會發生
chmod( $ARGV[0], $filename ); # wrong, even if "0644"
chmod( oct($ARGV[0]), $filename ); # correct, treat string as octal
你可以透過以八進制表示法印出正在使用的值,來隨時檢查它是否與你認為應該是什麼相符。以八進制和十進制格式印出
printf "0%o %d", $number, $number;
請記住,int()
僅會朝 0 截斷。若要四捨五入到特定位數,sprintf()
或 printf()
通常是最簡單的方法。
printf("%.3f", 3.1415926535); # prints 3.142
POSIX 模組(Perl 標準發行版的一部分)實作了 ceil()
、floor()
和許多其他數學和三角函式。
use POSIX;
my $ceil = ceil(3.5); # 4
my $floor = floor(3.5); # 3
在 5.000 到 5.003 的 Perl 中,三角函式是在 Math::Complex 模組中完成的。在 5.004 中,Math::Trig 模組(Perl 標準發行版的一部分)實作了三角函式。它在內部使用 Math::Complex 模組,而且有些函式可以從實軸中斷到複數平面,例如 2 的反正弦。
財務應用程式中的四捨五入可能會造成嚴重的影響,且應精確指定所使用的四捨五入方法。在這些情況下,可能不要相信 Perl 使用的任何四捨五入系統,而是自行實作所需的四捨五入函數。
要了解原因,請注意在中點交替時仍會遇到問題
for (my $i = -5; $i <= 5; $i += 0.5) { printf "%.0f ",$i }
-5 -4 -4 -4 -3 -2 -2 -2 -1 -0 0 0 1 2 2 2 3 4 4 4 5
別怪 Perl。它與 C 中的相同。IEEE 表示我們必須這麼做。絕對值為 2**31 以下整數的 Perl 數字(在 32 位元機器上)會像數學整數一樣運作。其他數字並無保證。
與 Perl 一樣,總是有多種方法可以執行。以下是進行數字表示法之間常見轉換的一些方法範例。這旨在具有代表性,而非詳盡無遺。
稍後 perlfaq4 中的一些範例會使用 CPAN 中的 Bit::Vector 模組。選擇 Bit::Vector 而非內建 Perl 函數的原因在於它可處理任何大小的數字,它針對某些作業進行了速度最佳化,且至少對某些程式設計人員而言,符號可能很熟悉。
使用 perl 內建的 0x
符號轉換
my $dec = 0xDEADBEEF;
使用 hex
函數
my $dec = hex("DEADBEEF");
使用 pack
my $dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
使用 CPAN 模組 Bit::Vector
use Bit::Vector;
my $vec = Bit::Vector->new_Hex(32, "DEADBEEF");
my $dec = $vec->to_Dec();
使用 sprintf
my $hex = sprintf("%X", 3735928559); # upper case A-F
my $hex = sprintf("%x", 3735928559); # lower case a-f
使用 unpack
my $hex = unpack("H*", pack("N", 3735928559));
使用 Bit::Vector
use Bit::Vector;
my $vec = Bit::Vector->new_Dec(32, -559038737);
my $hex = $vec->to_Hex();
而且 Bit::Vector 支援奇數位元計數
use Bit::Vector;
my $vec = Bit::Vector->new_Dec(33, 3735928559);
$vec->Resize(32); # suppress leading 0 if unwanted
my $hex = $vec->to_Hex();
使用 Perl 內建的數字轉換,其中數字前面有零
my $dec = 033653337357; # note the leading 0!
使用 oct
函數
my $dec = oct("33653337357");
使用 Bit::Vector
use Bit::Vector;
my $vec = Bit::Vector->new(32);
$vec->Chunk_List_Store(3, split(//, reverse "33653337357"));
my $dec = $vec->to_Dec();
使用 sprintf
my $oct = sprintf("%o", 3735928559);
使用 Bit::Vector
use Bit::Vector;
my $vec = Bit::Vector->new_Dec(32, -559038737);
my $oct = reverse join('', $vec->Chunk_List_Read(3));
Perl 5.6 讓你可以使用 0b
符號直接撰寫二進位數字
my $number = 0b10110110;
使用 oct
my $input = "10110110";
my $decimal = oct( "0b$input" );
使用 pack
和 ord
my $decimal = ord(pack('B8', '10110110'));
使用 pack
和 unpack
處理較長的字串
my $int = unpack("N", pack("B32",
substr("0" x 32 . "11110101011011011111011101111", -32)));
my $dec = sprintf("%d", $int);
# substr() is used to left-pad a 32-character string with zeros.
使用 Bit::Vector
my $vec = Bit::Vector->new_Bin(32, "11011110101011011011111011101111");
my $dec = $vec->to_Dec();
使用 sprintf
(perl 5.6 以上版本)
my $bin = sprintf("%b", 3735928559);
使用 unpack
my $bin = unpack("B*", pack("N", 3735928559));
使用 Bit::Vector
use Bit::Vector;
my $vec = Bit::Vector->new_Dec(32, -559038737);
my $bin = $vec->to_Bin();
其餘的轉換(例如十六進位 -> 八進位、二進位 -> 十六進位等)留待有興趣的讀者練習。
二進位算術運算子的行為取決於它們是使用在數字還是字串上。運算子將字串視為一系列位元並以此運作(字串 "3"
是位元模式 00110011
)。運算子使用數字的二進位形式(數字 3
被視為位元模式 00000011
)。
因此,11 & 3
對數字執行「與」運算(產生 3
)。"11" & "3"
對字串執行「與」運算(產生 "1"
)。
&
和 |
的大多數問題都發生在程式設計師認為他們有數字但實際上是字串,或反之亦然。為避免此問題,請明確地將引數字串化(使用 ""
或 qq()
)或將其明確地轉換為數字(使用 0+$arg
)。其餘問題發生在程式設計師說
if ("\020\020" & "\101\101") {
# ...
}
但由兩個空位元組組成的字串("\020\020" & "\101\101"
的結果)在 Perl 中不是假值。你需要
if ( ("\020\020" & "\101\101") !~ /[^\000]/) {
# ...
}
使用 Math::Matrix 或 Math::MatrixReal 模組(可從 CPAN 取得)或 PDL 延伸模組(也可從 CPAN 取得)。
若要對陣列中的每個元素呼叫函式並收集結果,請使用
my @results = map { my_func($_) } @array;
例如
my @triple = map { 3 * $_ } @single;
若要對陣列中的每個元素呼叫函式,但忽略結果
foreach my $iterator (@array) {
some_func($iterator);
}
若要對(小)範圍內的每個整數呼叫函式,可以使用
my @results = map { some_func($_) } (5 .. 25);
但你應該知道,在此形式中,..
運算子會建立範圍內所有整數的清單,這可能會佔用大量記憶體。但是,在 for
迴圈中使用 ..
時不會發生此問題,因為在這種情況下,範圍運算子會最佳化為反覆運算範圍,而不會建立整個清單。所以
my @results = ();
for my $i (5 .. 500_005) {
push(@results, some_func($i));
}
甚至
push(@results, some_func($_)) for 5 .. 500_005;
不會建立 500,000 個整數的中間清單。
取得 http://www.cpan.org/modules/by-module/Roman 模組。
如果您使用的是 5.004 之前的 Perl 版本,您必須在程式開始時呼叫 srand
一次,以設定亂數產生器的種子。
BEGIN { srand() if $] < 5.004 }
5.004 和之後的版本會在開始時自動呼叫 srand
。不要呼叫 srand
超過一次,否則會讓您的數字更不隨機,而不是更隨機。
電腦擅長預測,不擅長隨機(儘管程式中的錯誤會造成隨機的假象 :-)。Tom Phoenix 提供的 http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz 中「Far More Than You Ever Wanted To Know」系列中的 random 文章有更詳細的說明。John von Neumann 說:「任何人嘗試使用確定性方法產生亂數,當然是處於一種罪惡的狀態。」
Perl 仰賴底層系統來實作 rand
和 srand
;在某些系統上,產生的數字不夠隨機(特別是在 Windows 上:請參閱 http://www.perlmonks.org/?node_id=803632)。Math
名稱空間中的幾個 CPAN 模組實作了更好的偽亂數產生器;例如 Math::Random::MT(「Mersenne Twister」,快速)或 Math::TrulyRandom(使用系統計時器的瑕疵來產生亂數,速度較慢)。「Numerical Recipes in C」中有更多亂數演算法的說明,請參閱 http://www.nr.com/
若要在兩個值之間取得亂數,您可以使用內建的 rand()
在 0 和 1 之間取得亂數。然後,將其轉換為您想要的範圍。
rand($x)
會傳回一個數字,使得 0 <= rand($x) < $x
。因此,您要讓 Perl 找出的是在您的 X 和 Y 之間的差值的範圍內的亂數。
也就是說,若要取得 10 到 15 之間的數字(包含 10 和 15),您需要一個 0 到 5 之間的亂數,然後將其加到 10 上。
my $number = 10 + int rand( 15-10+1 ); # ( 10,11,12,13,14, or 15 )
因此,您可以推導出以下簡單的函式來抽象化這件事。它會在給定的兩個整數之間選擇一個亂數整數(包含兩個整數)。例如:random_int_between(50,120)
。
sub random_int_between {
my($min, $max) = @_;
# Assumes that the two arguments are integers themselves!
return $min if $min == $max;
($min, $max) = ($max, $min) if $min > $max;
return $min + int rand(1 + $max - $min);
}
一年中的第幾天在 localtime
函式傳回的清單中。若沒有引數,localtime
會使用目前時間。
my $day_of_year = (localtime)[7];
POSIX 模組也可以將日期格式化為一年中的第幾天或第幾週。
use POSIX qw/strftime/;
my $day_of_year = strftime "%j", localtime;
my $week_of_year = strftime "%W", localtime;
要取得任何日期的年分,請使用 POSIX 的 mktime
來取得 localtime
參數的紀元秒時間。
use POSIX qw/mktime strftime/;
my $week_of_year = strftime "%W",
localtime( mktime( 0, 0, 0, 18, 11, 87 ) );
您也可以使用 Time::Piece,它隨 Perl 附帶,並提供傳回物件的 localtime
use Time::Piece;
my $day_of_year = localtime->yday;
my $week_of_year = localtime->week;
Date::Calc 模組也提供兩個函數來計算這些值
use Date::Calc;
my $day_of_year = Day_of_Year( 1987, 12, 18 );
my $week_of_year = Week_of_Year( 1987, 12, 18 );
使用下列簡單函數
sub get_century {
return int((((localtime(shift || time))[5] + 1999))/100);
}
sub get_millennium {
return 1+int((((localtime(shift || time))[5] + 1899))/1000);
}
在某些系統上,POSIX 模組的 strftime()
函數已透過非標準方式擴充,以使用 %C
格式,他們有時宣稱這是「世紀」。它不是,因為在大多數此類系統上,這僅為四位數年份的前兩個數字,因此無法用來可靠地判斷目前的世紀或千禧年。
(由 brian d foy 貢獻)
您可以將所有日期儲存為數字,然後再減掉。不過,生活並不總是那麼簡單。
Time::Piece 模組隨 Perl 附帶,它以傳回物件的版本取代 localtime。它也重載比較運算子,讓您可以直接比較它們
use Time::Piece;
my $date1 = localtime( $some_time );
my $date2 = localtime( $some_other_time );
if( $date1 < $date2 ) {
print "The date was in the past\n";
}
您也可以透過減法取得差異,它會傳回 Time::Seconds 物件
my $date_diff = $date1 - $date2;
print "The difference is ", $date_diff->days, " days\n";
如果您想處理格式化的日期,Date::Manip、Date::Calc 或 DateTime 模組可以提供協助。
如果字串夠規律,它總是具有相同的格式,您可以將其拆分,並將部分傳遞給標準 Time::Local 模組中的 timelocal
。否則,您應該查看 CPAN 中的 Date::Calc、Date::Parse 和 Date::Manip 模組。
(由 brian d foy 和 Dave Cross 提供)
您可以使用 Time::Piece 模組,它是標準函式庫的一部分,它可以將日期/時間轉換為儒略日
$ perl -MTime::Piece -le 'print localtime->julian_day'
2455607.7959375
或修正儒略日
$ perl -MTime::Piece -le 'print localtime->mjd'
55607.2961226851
甚至今年的第幾天(有些人認為這是儒略日)
$ perl -MTime::Piece -le 'print localtime->yday'
45
您也可以使用 DateTime 模組執行相同的操作
$ perl -MDateTime -le'print DateTime->today->jd'
2453401.5
$ perl -MDateTime -le'print DateTime->today->mjd'
53401
$ perl -MDateTime -le'print DateTime->today->doy'
31
您可以使用 CPAN 上提供的 Time::JulianDay 模組。不過,請確定您真的想要找出儒略日,因為許多人對儒略日有不同的看法(例如,請參閱 http://www.hermetic.ch/cal_stud/jdn.htm)
$ perl -MTime::JulianDay -le 'print local_julian_day( time )'
55608
(由 brian d foy 貢獻)
若要正確執行,您可以使用其中一個 Date
模組,因為它們使用日曆而不是時間。 DateTime 模組讓這件事變得簡單,而且會提供給您同一天的時間,只有日期是前一天,即使有夏令時間變更也不受影響
use DateTime;
my $yesterday = DateTime->now->subtract( days => 1 );
print "Yesterday was $yesterday\n";
您也可以使用 Date::Calc 模組及其 Today_and_Now
函式。
use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );
print "@date_time\n";
大多數人會嘗試使用時間而不是日曆來找出日期,但這假設每一天都是二十四小時。對大多數人來說,一年中有兩天不是這樣:切換至夏令時間和從夏令時間切換回來會造成時間混亂。例如,其他建議有時會出錯
從 Perl 5.10 開始, Time::Piece 和 Time::Seconds 成為標準發行的一部分,因此您可能會認為可以執行類似下列的動作
use Time::Piece;
use Time::Seconds;
my $yesterday = localtime() - ONE_DAY; # WRONG
print "Yesterday was $yesterday\n";
Time::Piece 模組會匯出一個新的 localtime
,它會傳回一個物件,而 Time::Seconds 會匯出 ONE_DAY
常數,它是一個設定好的秒數。這表示它總是會提供 24 小時前的那個時間,但那並不總是昨天。當夏令時間結束時,可能會造成問題,因為會有一天的長度是 25 小時。
您在使用 Time::Local 時也會遇到相同的問題,它會在那些特殊情況下提供錯誤的答案
# contributed by Gunnar Hjalmarsson
use Time::Local;
my $today = timelocal 0, 0, 12, ( localtime )[3..5];
my ($d, $m, $y) = ( localtime $today-86400 )[3..5]; # WRONG
printf "Yesterday: %d-%02d-%02d\n", $y+1900, $m+1, $d;
(由 brian d foy 貢獻)
Perl 本身從未有過 Y2K 問題,儘管這從未阻止人們自行製造 Y2K 問題。請參閱 localtime
的文件,了解其正確用法。
從 Perl 5.12 開始,localtime
和 gmtime
可以處理 2038 年 1 月 19 日上午 03:14:08 之後(32 位元時間會溢位)的日期。您在 32 位元 perl
上仍可能會收到警告
% perl5.12 -E 'say scalar localtime( 0x9FFF_FFFFFFFF )'
Integer overflow in hexadecimal number at -e line 1.
Wed Nov 1 19:42:39 5576711
在 64 位元 perl
上,您可以為那些執行時間非常長的專案取得更大的日期
% perl5.12 -E 'say scalar gmtime( 0x9FFF_FFFFFFFF )'
Thu Nov 2 00:42:39 5576711
不過,如果您需要追蹤衰變質子,您仍然會遇到問題。
(由 brian d foy 貢獻)
有許多方法可以確保值符合您的預期或願意接受。除了我們在 perlfaq 中介紹的特定範例外,您還可以查看名稱中包含「Assert」和「Validate」的模組,以及其他模組,例如 Regexp::Common。
有些模組針對特定類型的輸入進行驗證,例如 Business::ISBN、Business::CreditCard、Email::Valid 和 Data::Validate::IP。
這取決於您對「跳脫」的定義。網址跳脫在 perlfaq9 中有說明。使用反斜線 (\
) 字元的 shell 跳脫則使用下列方式移除:
s/\\(.)/$1/g;
這不會展開 "\n"
或 "\t"
或任何其他特殊跳脫。
(由 brian d foy 貢獻)
您可以使用取代運算子來尋找字元對(或字元執行)並將它們取代為單一執行個體。在此取代中,我們在 (.)
中尋找一個字元。記憶括弧將匹配的字元儲存在反向參照 \g1
中,我們使用它來要求相同的事物緊接在後。我們使用 $1
中的字元取代字串的該部分。
s/(.)\g1/$1/g;
我們也可以使用轉寫運算子 tr///
。在此範例中,我們的 tr///
的搜尋清單側沒有任何內容,但 c
選項會補充它,因此它包含所有內容。取代清單也不包含任何內容,因此轉寫幾乎是一個無操作,因為它不會進行任何取代(或更確切地說,將字元取代為它本身)。但是,s
選項會壓縮字串中重複和連續的字元,因此字元不會出現在自身旁邊
my $str = 'Haarlem'; # in the Netherlands
$str =~ tr///cs; # Now Harlem, like in New York
(由 brian d foy 貢獻)
這在 perlref 中有說明,雖然它不是最容易閱讀的東西,但它確實有效。在這些範例的每一個中,我們都呼叫大括弧內部的函式,用於取消參照參照。如果我們有多個傳回值,我們可以建構並取消參照一個匿名陣列。在這種情況下,我們在清單內容中呼叫函式。
print "The time values are @{ [localtime] }.\n";
如果我們想要在標量語境中呼叫函數,我們必須做更多工作。我們可以在大括號內放置任何我們喜歡的程式碼,所以我們只需要以標量參照結束,儘管你如何做到這一點取決於你,你可以在大括號內使用程式碼。請注意,使用括號會建立一個清單語境,所以我們需要 scalar
來強制函數的標量語境
print "The time is ${\(scalar localtime)}.\n"
print "The time is ${ my $x = localtime; \$x }.\n";
如果你的函數已經傳回一個參照,你不需要自己建立參照。
sub timestamp { my $t = localtime; \$t }
print "The time is ${ timestamp() }.\n";
Interpolation
模組也可以為你做很多神奇的事情。你可以指定一個變數名稱,在本例中為 E
,以設定一個繫結雜湊,為你執行內插。它還有其他幾個方法可以做到這一點。
use Interpolation E => 'eval';
print "The time values are $E{localtime()}.\n";
在大多數情況下,使用字串串接可能更容易,它也會強制標量語境。
print "The time is " . localtime() . ".\n";
要在兩個單一字元之間尋找東西,像 /x([^x]*)x/
這樣的模式會在 $1 中得到中間位元。對於多個,則需要更像 /alpha(.*?)omega/
的東西。對於巢狀模式和/或平衡表達式,請參閱所謂的 (?PARNO) 建構(自 perl 5.10 起可用)。CPAN 模組 Regexp::Common 可以幫助建立這樣的正規表示式(特別是 Regexp::Common::balanced 和 Regexp::Common::delimited)。
更複雜的情況需要寫一個解析器,可能使用 CPAN 中的解析模組,例如 Regexp::Grammars、Parse::RecDescent、Parse::Yapp、Text::Balanced 或 Marpa::R2。
在標量語境中使用 reverse()
,如 "reverse" in perlfunc 中所述。
my $reversed = reverse $string;
你可以自己做
1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
或者,您也可以使用 Text::Tabs 模組(標準 Perl 發行版的一部分)。
use Text::Tabs;
my @expanded_lines = expand(@lines_with_tabs);
使用 Text::Wrap(標準 Perl 發行版的一部分)
use Text::Wrap;
print wrap("\t", ' ', @paragraphs);
您提供給 Text::Wrap 的段落不應包含內嵌換行符號。 Text::Wrap 不會對齊行(右對齊)。
或者使用 CPAN 模組 Text::Autoformat。可以透過建立 shell 別名來輕鬆完成格式化檔案,如下所示
alias fmt="perl -i -MText::Autoformat -n0777 \
-e 'print autoformat $_, {all=>1}' $*"
參閱 Text::Autoformat 的文件,以了解其眾多功能。
您可以使用 substr() 存取字串的第一個字元。例如,要取得第一個字元,請從位置 0 開始,並擷取長度為 1 的字串。
my $string = "Just another Perl Hacker";
my $first_char = substr( $string, 0, 1 ); # 'J'
若要變更字串的一部分,可以使用第四個選用引數,即替換字串。
substr( $string, 13, 4, "Perl 5.8.0" );
您也可以將 substr() 用作左值。
substr( $string, 13, 4 ) = "Perl 5.8.0";
您必須自行追蹤 N。例如,假設您想將 "whoever"
或 "whomever"
的第五個出現變更為 "whosoever"
或 "whomsoever"
,且不分大小寫。這些都假設 $_ 包含要變更的字串。
$count = 0;
s{((whom?)ever)}{
++$count == 5 # is it the 5th?
? "${2}soever" # yes, swap
: $1 # renege and leave it there
}ige;
在更一般的情況下,您可以在 while
迴圈中使用 /g
修飾詞,並計算匹配次數。
$WANT = 3;
$count = 0;
$_ = "One fish two fish red fish blue fish";
while (/(\w+)\s+fish\b/gi) {
if (++$count == $WANT) {
print "The third fish is a $1 one.\n";
}
}
這會列印出:"The third fish is a red one."
您也可以使用重複計數和重複模式,如下所示
/(?:\w+\s+fish\s+){2}(\w+)\s+fish/i;
有許多方法,效率不一。如果您想計算字串中某個單一字元 (X) 的計數,可以使用 tr///
函數,如下所示
my $string = "ThisXlineXhasXsomeXx'sXinXit";
my $count = ($string =~ tr/X//);
print "There are $count X characters in the string";
如果您只是在尋找單一字元,這是很好的方法。但是,如果您嘗試在較大的字串中計算多個字元子字串,tr///
將無法使用。您可以執行的方式是將 while() 迴圈包圍在全域模式匹配中。例如,讓我們計算負整數
my $string = "-9 55 48 -2 23 -76 4 14 -44";
my $count = 0;
while ($string =~ /-\d+/g) { $count++ }
print "There are $count negative numbers in the string";
另一個版本在清單內容中使用全域匹配,然後將結果指派給純量,產生匹配次數的計數。
my $count = () = $string =~ /-\d+/g;
(由 brian d foy 貢獻)
Damian Conway 的 Text::Autoformat 會為您處理所有思考。
use Text::Autoformat;
my $x = "Dr. Strangelove or: How I Learned to Stop ".
"Worrying and Love the Bomb";
print $x, "\n";
for my $style (qw( sentence title highlight )) {
print autoformat($x, { case => $style }), "\n";
}
您想要如何將這些字詞大寫?
FRED AND BARNEY'S LODGE # all uppercase
Fred And Barney's Lodge # title case
Fred and Barney's Lodge # highlight case
這並不像看起來那麼容易。您認為裡面有多少個字詞?稍等一下... 再稍等一下.... 如果您回答 5,您答對了。Perl 字詞是 \w+
的群組,但那不是您想要大寫的。Perl 怎麼知道不要將撇號後的 s
大寫?您可以嘗試正規表示式
$string =~ s/ (
(^\w) #at the beginning of the line
| # or
(\s\w) #preceded by whitespace
)
/\U$1/xg;
$string =~ s/([\w']+)/\u\L$1/g;
現在,如果您不想將那個「and」大寫呢?只要使用 Text::Autoformat,然後繼續處理下一個問題。 :)
有幾個模組可以處理這種剖析,包括 Text::Balanced、Text::CSV、Text::CSV_XS 和 Text::ParseWords 等。
舉例來說,嘗試將一個以逗號分隔的字串分割成不同的欄位。您不能使用 split(/,/)
,因為當逗號在引號內時,您不應該分割。例如,取用類似這樣的資料行
SAR001,"","Cimetrix, Inc","Bob Smith","CAM",N,8,1,0,7,"Error, Core Dumped"
由於引號的限制,這是一個相當複雜的問題。感謝《Mastering Regular Expressions》的作者 Jeffrey Friedl 為我們處理這些問題。他建議(假設您的字串包含在 $text
中)
my @new = ();
push(@new, $+) while $text =~ m{
"([^\"\\]*(?:\\.[^\"\\]*)*)",? # groups the phrase inside the quotes
| ([^,]+),?
| ,
}gx;
push(@new, undef) if substr($text,-1,1) eq ',';
如果您想要在引號分隔的欄位內表示引號,請使用反斜線進行跳脫(例如,"like \"this\""
)。
或者,Text::ParseWords 模組(標準 Perl 發行版的一部分)讓您可以說
use Text::ParseWords;
@new = quotewords(",", 0, $text);
不過,對於剖析或產生 CSV,強烈建議使用 Text::CSV,而不是自己實作;只要使用多年來已在實際環境中經過測試和驗證的程式碼,您就可以避免日後出現奇怪的錯誤。
(由 brian d foy 貢獻)
替換可以為您執行此操作。對於單一行,您想要將所有前導或尾隨空白取代為空白。您可以使用一對替換來執行此操作
s/^\s+//;
s/\s+$//;
您也可以將其寫成單一替換,儘管事實證明合併陳述比個別陳述慢。不過,這對您來說可能並不重要
s/^\s+|\s+$//g;
在此正規表示式中,交替匹配字串的開頭或結尾,因為錨定符號的優先順序低於交替。使用 /g
旗標,替換會執行所有可能的匹配,因此它會取得兩者。請記住,尾隨換行符號與 \s+
匹配,而 $
錨定符號可以匹配到字串的絕對結尾,因此換行符號也會消失。只要將換行符號新增到輸出即可,這具有保留「空白」(完全由空白組成)行的額外好處,而 ^\s+
本身會移除所有這些空白行
while( <> ) {
s/^\s+|\s+$//g;
print "$_\n";
}
對於多行字串,您可以透過加入 /m
旗標(代表「多行」)將正規表達式套用至字串中的每個邏輯行。使用 /m
旗標時,$
會比對嵌入式換行符號之前,因此不會移除換行符號。此模式仍會移除字串結尾的換行符號
$string =~ s/^\s+|\s+$//gm;
請記住,完全由空白組成的行將會消失,因為交替運算的第一部分可以比對整個字串並將其替換為空白。如果您需要保留嵌入式空白行,您必須多做一些工作。與其比對任何空白(包含換行符號),只要比對其他空白即可
$string =~ s/^[\t\f ]+|[\t\f ]+$//mg;
在以下範例中,$pad_len
為您希望填充字串的長度,$text
或 $num
包含要填充的字串,而 $pad_char
包含填充字元。如果您事先知道填充字元,您可以使用單一字元字串常數取代 $pad_char
變數。同樣地,如果您事先知道填充長度,您可以使用整數取代 $pad_len
。
最簡單的方法是使用 sprintf
函數。它可以在左側或右側使用空白填充,在左側使用零填充,而且不會截斷結果。pack
函數只能在右側使用空白填充字串,而且會將結果截斷至 $pad_len
的最大長度。
# Left padding a string with blanks (no truncation):
my $padded = sprintf("%${pad_len}s", $text);
my $padded = sprintf("%*s", $pad_len, $text); # same thing
# Right padding a string with blanks (no truncation):
my $padded = sprintf("%-${pad_len}s", $text);
my $padded = sprintf("%-*s", $pad_len, $text); # same thing
# Left padding a number with 0 (no truncation):
my $padded = sprintf("%0${pad_len}d", $num);
my $padded = sprintf("%0*d", $pad_len, $num); # same thing
# Right padding a string with blanks using pack (will truncate):
my $padded = pack("A$pad_len",$text);
如果您需要使用空白或零以外的字元填充,您可以使用下列方法之一。它們都會使用 x
運算子產生填充字串,並將其與 $text
結合。這些方法不會截斷 $text
。
使用任何字元在左右兩側填充,建立新的字串
my $padded = $pad_char x ( $pad_len - length( $text ) ) . $text;
my $padded = $text . $pad_char x ( $pad_len - length( $text ) );
使用任何字元在左右兩側填充,直接修改 $text
substr( $text, 0, 0 ) = $pad_char x ( $pad_len - length( $text ) );
$text .= $pad_char x ( $pad_len - length( $text ) );
(由 brian d foy 貢獻)
如果您知道包含資料的欄位,您可以使用 substr
擷取單一欄位。
my $column = substr( $line, $start_column, $length );
如果您知道欄位由空白或其他分隔符號分隔,您可以使用 split
,只要空白或分隔符號不會出現在資料中即可。
my $line = ' fred barney betty ';
my @columns = split /\s+/, $line;
# ( '', 'fred', 'barney', 'betty' );
my $line = 'fred||barney||betty';
my @columns = split /\|/, $line;
# ( 'fred', '', 'barney', '', 'betty' );
如果您想處理以逗號分隔的值,請勿執行此操作,因為該格式有點複雜。請使用處理該格式的其中一個模組,例如 Text::CSV、Text::CSV_XS 或 Text::CSV_PP。
如果您想拆分一整行固定欄位,您可以使用帶有 A(ASCII)格式的 unpack
。透過在格式指定符號後使用數字,您可以表示欄位寬度。請參閱 perlfunc 中的 pack
和 unpack
條目,以取得更多詳細資料。
my @fields = unpack( $line, "A8 A8 A8 A16 A4" );
請注意,unpack
格式引數中的空白不表示實際的空白。如果您有以空白分隔的資料,您可能需要使用 split
。
(由 brian d foy 貢獻)
您可以使用 Text::Soundex
模組。如果您想執行模糊或近似比對,您也可以嘗試 String::Approx、Text::Metaphone 和 Text::DoubleMetaphone 模組。
(由 brian d foy 貢獻)
如果您能避免,就不要這樣做,或者如果您能使用範本系統,例如 Text::Template 或 Template Toolkit,請改用這些系統。您甚至可以使用 sprintf
或 printf
完成這項工作
my $string = sprintf 'Say hello to %s and %s', $foo, $bar;
不過,對於不想拉出完整範本系統的單次簡單案例,我會使用包含兩個 Perl 標量變數的字串。在此範例中,我想將 $foo
和 $bar
展開為其變數值
my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar';
我可以執行此操作的方法之一包括替換運算子以及雙重 /e
旗標。第一個 /e
會評估替換側的 $1
,並將其轉換為 $foo
。第二個 /e 從 $foo
開始,並將其替換為其值。然後,$foo
會轉換為 'Fred',而這最後就是字串中剩下的內容
$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney'
/e
也會靜默地忽略嚴格的違規,用空字串取代未定義的變數名稱。由於我使用 /e
標記(甚至兩次!),我遇到與 eval
字串形式相同的安全性問題。如果 $foo
中有奇怪的東西,例如 @{[ system "rm -rf /" ]}
,那麼我可能會遇到麻煩。
為了解決安全性問題,我也可以從雜湊中提取值,而不是評估變數名稱。使用單個 /e
,我可以檢查雜湊以確保值存在,如果不存在,我可以將遺失的值替換為標記,在本例中為 ???
,表示我錯過了某些東西
my $string = 'This has $foo and $bar';
my %Replacements = (
foo => 'Fred',
);
# $string =~ s/\$(\w+)/$Replacements{$1}/g;
$string =~ s/\$(\w+)/
exists $Replacements{$1} ? $Replacements{$1} : '???'
/eg;
print $string;
與其他語言不同,Perl 允許你在雙引號字串中裸露地嵌入變數,例如 "variable $variable"
。當變數名稱後面沒有空格或其他非字元時,你可以新增大括號(例如 "foo ${foo}bar"
)以確保正確解析。
陣列也可以直接嵌入字串中,並且預設會在元素之間擴充空格。預設的 LIST_SEPARATOR 可以透過將不同的字串指定給特殊變數 $"
來變更,例如 local $" = ', ';
。
Perl 也支援字串中的參考,提供與其他兩種語言中功能等效的功能。
嵌入在字串中的 ${\ ... }
會適用於大多數簡單陳述,例如物件->方法呼叫。更複雜的程式碼可以包裝在 do 區塊 ${\ do{...} }
中。
當你希望根據 $"
擴充清單時,請使用 @{[ ... ]}
。
use Time::Piece;
use Time::Seconds;
my $scalar = 'STRING';
my @array = ( 'zorro', 'a', 1, 'B', 3 );
# Print the current date and time and then Tommorrow
my $t = Time::Piece->new;
say "Now is: ${\ $t->cdate() }";
say "Tomorrow: ${\ do{ my $T=Time::Piece->new + ONE_DAY ; $T->fullday }}";
# some variables in strings
say "This is some scalar I have $scalar, this is an array @array.";
say "You can also write it like this ${scalar} @{array}.";
# Change the $LIST_SEPARATOR
local $" = ':';
say "Set \$\" to delimit with ':' and sort the Array @{[ sort @array ]}";
你可能還想查看模組 Quote::Code,以及範本工具,例如 Template::Toolkit 和 Mojo::Template。
另請參閱:"如何在文字字串中擴充變數?" 和 "如何在字串中擴充函式呼叫?",在這個常見問題解答中。
問題在於這些雙引號強制字串化——將數字和參考強制轉換為字串——即使你不想讓它們成為字串。這樣想:雙引號擴充用於產生新的字串。如果你已經有一個字串,為什麼需要更多?
如果你習慣寫像這些奇怪的東西
print "$var"; # BAD
my $new = "$old"; # BAD
somefunc("$var"); # BAD
你會有麻煩。在 99.8% 的情況下,它們應該是更簡單、更直接的
print $var;
my $new = $old;
somefunc($var);
否則,除了會讓你的速度變慢之外,當標量中的東西實際上既不是字串也不是數字,而是參考時,你將會中斷程式碼
func(\@array);
sub func {
my $aref = shift;
my $oref = "$aref"; # WRONG
}
你也可以在 Perl 中的少數幾個實際上會在意字串和數字之間差異的運算中遇到微妙的問題,例如神奇的 ++
自動遞增運算子或 syscall() 函式。
字串化也會破壞陣列。
my @lines = `command`;
print "@lines"; # WRONG - extra blanks
print @lines; # right
可以在 perlop 中找到 HERE 文件。檢查這三件事
如果你想縮排 HERE 文件中的文字,你可以這樣做
# all in one
(my $VAR = <<HERE_TARGET) =~ s/^\s+//gm;
your text
goes here
HERE_TARGET
但 HERE_TARGET 仍必須與邊界齊平。如果你也想要縮排,你必須在縮排中加上引號。
(my $quote = <<' FINIS') =~ s/^\s+//gm;
...we will have peace, when you and all your works have
perished--and the works of your dark master to whom you
would deliver us. You are a liar, Saruman, and a corrupter
of men's hearts. --Theoden in /usr/src/perl/taint.c
FINIS
$quote =~ s/\s+--/\n--/;
以下是縮排 HERE 文件的良好通用修正函式。它預期會以 HERE 文件作為其引數呼叫。它會查看每一行是否以共同的子字串開頭,如果是,就會移除該子字串。否則,它會取得第一行中找到的前導空白量,並從後續每一行中移除該空白量。
sub fix {
local $_ = shift;
my ($white, $leader); # common whitespace and common leading string
if (/^\s*(?:([^\w\s]+)(\s*).*\n)(?:\s*\g1\g2?.*\n)+$/) {
($white, $leader) = ($2, quotemeta($1));
} else {
($white, $leader) = (/^(\s+)/, '');
}
s/^\s*?$leader(?:$white)?//gm;
return $_;
}
這適用於動態確定的前導特殊字串
my $remember_the_main = fix<<' MAIN_INTERPRETER_LOOP';
@@@ int
@@@ runops() {
@@@ SAVEI32(runlevel);
@@@ runlevel++;
@@@ while ( op = (*op->op_ppaddr)() );
@@@ TAINT_NOT;
@@@ return 0;
@@@ }
MAIN_INTERPRETER_LOOP
或適用於固定量的前導空白,並正確保留剩餘的縮排
my $poem = fix<<EVER_ON_AND_ON;
Now far ahead the Road has gone,
And I must follow, if I can,
Pursuing it with eager feet,
Until it joins some larger way
Where many paths and errands meet.
And whither then? I cannot say.
--Bilbo in /usr/src/perl/pp_ctl.c
EVER_ON_AND_ON
從 Perl 版本 5.26 開始,語言中新增了一種更簡單、更簡潔的方式來撰寫縮排 HERE 文件:波浪號 (~) 修飾子。有關詳細資訊,請參閱 "perlop 中的「縮排 Here-docs」。
(由 brian d foy 貢獻)
清單是標量的固定集合。陣列是包含標量可變集合的變數。陣列可以提供其集合進行清單運算,因此清單運算也適用於陣列
# slices
( 'dog', 'cat', 'bird' )[2,3];
@animals[2,3];
# iteration
foreach ( qw( dog cat bird ) ) { ... }
foreach ( @animals ) { ... }
my @three = grep { length == 3 } qw( dog cat bird );
my @three = grep { length == 3 } @animals;
# supply an argument list
wash_animals( qw( dog cat bird ) );
wash_animals( @animals );
陣列運算會變更標量、重新排列標量或新增或減去一些標量,僅適用於陣列。這些運算無法用於固定的清單。陣列運算包括 shift
、unshift
、push
、pop
和 splice
。
陣列也可以改變其長度
$#animals = 1; # truncate to two elements
$#animals = 10000; # pre-extend to 10,001 elements
你可以改變陣列元素,但不能改變清單元素
$animals[0] = 'Rottweiler';
qw( dog cat bird )[0] = 'Rottweiler'; # syntax error!
foreach ( @animals ) {
s/^d/fr/; # works fine
}
foreach ( qw( dog cat bird ) ) {
s/^d/fr/; # Error! Modification of read only value!
}
然而,如果清單元素本身是一個變數,看起來你就可以改變清單元素。但是,清單元素是變數,而不是資料。你改變的不是清單元素,而是清單元素所指涉的內容。清單元素本身並未改變:它仍然是同一個變數。
你還必須注意上下文。你可以將陣列指定給一個純量,以取得陣列中的元素數量。不過,這只適用於陣列
my $count = @animals; # only works with arrays
如果你嘗試對你認為是清單的東西做同樣的事情,你會得到一個完全不同的結果。雖然在右手邊看起來像一個清單,但 Perl 實際上看到的是一堆以逗號分隔的純量
my $scalar = ( 'dog', 'cat', 'bird' ); # $scalar gets bird
由於你指定給一個純量,右手邊處於純量上下文中。逗號運算子(是的,它是一個運算子!)在純量上下文中會評估其左手邊,丟棄結果,並評估其右手邊並傳回結果。實際上,那個看起來像清單的東西會將其最右邊的值指定給 $scalar
。許多人會搞砸這件事,因為他們選擇最後一個元素也是他們預期的計數的清單類似物
my $scalar = ( 1, 2, 3 ); # $scalar gets 3, accidentally
(由 brian d foy 貢獻)
不同之處在於符號,也就是陣列名稱前面的那個特殊字元。$
符號表示「正好一個項目」,而 @
符號表示「零個或多個項目」。$
會取得一個單一純量,而 @
會取得一個清單。
混淆產生於人們錯誤地假設符號表示變數類型。
$array[1]
是對陣列的單一元素存取。它會傳回索引 1 中的項目(如果沒有項目,則傳回 undef)。如果你打算從陣列中取得正好一個元素,你應該使用此形式。
@array[1]
是陣列切片,儘管它只有一個索引。你可以透過指定其他索引作為清單(例如 @array[1,4,3,0]
)同時取出多個元素。
在指定的一側使用切片會為右手邊提供清單上下文。這可能會導致意外的結果。例如,如果你想從檔案句柄中讀取單一行,指定給純量值就可以了
$array[1] = <STDIN>;
然而,在清單上下文中,行輸入運算子會將所有行作為清單傳回。第一行進入 @array[1]
,而其餘行則神秘地消失了
@array[1] = <STDIN>; # most likely not what you want
use warnings
指令或 -w 旗標會在你使用具有單一索引的陣列切片時警告你。
(由 brian d foy 貢獻)
使用雜湊。當你想到「唯一」或「重複」這些字時,請想到「雜湊鍵」。
如果你不在乎元素的順序,你可以建立雜湊,然後擷取鍵。建立雜湊的方式並不重要:只要使用 keys
取得唯一元素即可。
my %hash = map { $_, 1 } @array;
# or a hash slice: @hash{ @array } = ();
# or a foreach: $hash{$_} = 1 foreach ( @array );
my @unique = keys %hash;
如果你想使用模組,請嘗試 List::MoreUtils 中的 uniq
函式。在清單內容中,它會傳回唯一元素,並保留它們在清單中的順序。在純量內容中,它會傳回唯一元素的數量。
use List::MoreUtils qw(uniq);
my @unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 1,2,3,4,5,6,7
my $unique = uniq( 1, 2, 3, 4, 4, 5, 6, 5, 7 ); # 7
你也可以逐一檢視每個元素,並略過你之前看過的元素。使用雜湊來追蹤。第一次迴圈看到元素時,該元素在 %Seen
中沒有鍵。next
陳述式會建立鍵,並立即使用它的值,即 undef
,因此迴圈會繼續執行 push
,並增加該鍵的值。下次迴圈看到同一個元素時,它的鍵存在於雜湊中,而且該鍵的值為 true(因為它不是 0 或 undef
),因此下一次會略過該反覆運算,而迴圈會繼續執行下一個元素。
my @unique = ();
my %seen = ();
foreach my $elem ( @array ) {
next if $seen{ $elem }++;
push @unique, $elem;
}
你可以使用 grep 來更簡潔地撰寫此程式碼,它會執行相同的工作。
my %seen = ();
my @unique = grep { ! $seen{ $_ }++ } @array;
(此答案的部分內容由 Anno Siegel 和 brian d foy 提供)
聽到「in」這個字表示你可能應該使用雜湊,而不是清單或陣列,來儲存你的資料。雜湊的設計目的是快速且有效地回答這個問題。陣列則不然。
話雖如此,有幾種方法可以解決這個問題。如果你打算多次對任意字串值執行這個查詢,最快的辦法可能是反轉原始陣列,並維護一個雜湊,其鍵是第一個陣列的值
my @blues = qw/azure cerulean teal turquoise lapis-lazuli/;
my %is_blue = ();
for (@blues) { $is_blue{$_} = 1 }
現在你可以檢查 $is_blue{$some_color}
。一開始就把所有藍色都放在雜湊中可能是一個好主意。
如果所有值都是小整數,你可以使用簡單的索引陣列。這種陣列會佔用較少的空間
my @primes = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
my @is_tiny_prime = ();
for (@primes) { $is_tiny_prime[$_] = 1 }
# or simply @istiny_prime[@primes] = (1) x @primes;
現在你檢查 $is_tiny_prime[$some_number]
。
如果問題中的值是整數而不是字串,你可以使用位元字串來節省大量空間
my @articles = ( 1..10, 150..2000, 2017 );
undef $read;
for (@articles) { vec($read,$_,1) = 1 }
現在檢查 vec($read,$n,1)
是否對某些 $n
為 true。
這些方法保證快速執行個別測試,但需要重新組織原始清單或陣列。只有當你必須針對同一個陣列測試多個值時,它們才會有用。
如果你只測試一次,標準模組 List::Util 會匯出 any
函式,用於此目的。它會在找到元素後停止。它使用 C 編寫以提升速度,而它的 Perl 等效項看起來像這個子程式
sub any (&@) {
my $code = shift;
foreach (@_) {
return 1 if $code->();
}
return 0;
}
如果速度不是主要考量,常見的慣用語法會在標量環境中使用 grep(會傳回通過其條件的項目數目)來遍歷整個清單。不過,這樣做的好處是會告訴你找到多少個符合條件的項目。
my $is_there = grep $_ eq $whatever, @array;
如果你想要實際擷取符合條件的元素,只要在清單環境中使用 grep 即可。
my @matches = grep $_ eq $whatever, @array;
使用雜湊。以下程式碼可以執行這兩個動作,甚至更多。它假設每個元素在給定的陣列中都是唯一的
my (@union, @intersection, @difference);
my %count = ();
foreach my $element (@array1, @array2) { $count{$element}++ }
foreach my $element (keys %count) {
push @union, $element;
push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
}
請注意,這是「對稱差集」,也就是 A 或 B 中的所有元素,但兩者都不存在。可以將它想成是 xor 運算。
以下程式碼適用於單層級陣列。它使用字串比較,並且不會區分已定義的空字串和未定義的空字串。如果你有其他需求,請自行修改。
$are_equal = compare_arrays(\@frogs, \@toads);
sub compare_arrays {
my ($first, $second) = @_;
no warnings; # silence spurious -w undef complaints
return 0 unless @$first == @$second;
for (my $i = 0; $i < @$first; $i++) {
return 0 if $first->[$i] ne $second->[$i];
}
return 1;
}
對於多層級結構,你可能希望使用更像這個的方法。它使用 CPAN 模組 FreezeThaw
use FreezeThaw qw(cmpStr);
my @a = my @b = ( "this", "that", [ "more", "stuff" ] );
printf "a and b contain %s arrays\n",
cmpStr(\@a, \@b) == 0
? "the same"
: "different";
這種方法也適用於比較雜湊。我們將在此示範兩個不同的答案
use FreezeThaw qw(cmpStr cmpStrHard);
my %a = my %b = ( "this" => "that", "extra" => [ "more", "stuff" ] );
$a{EXTRA} = \%b;
$b{EXTRA} = \%a;
printf "a and b contain %s hashes\n",
cmpStr(\%a, \%b) == 0 ? "the same" : "different";
printf "a and b contain %s hashes\n",
cmpStrHard(\%a, \%b) == 0 ? "the same" : "different";
第一個答案回報兩個雜湊都包含相同的資料,而第二個答案則回報它們不相同。你比較喜歡哪一個,就留給讀者自行練習。
若要找出滿足條件的第一個陣列元素,你可以使用 List::Util 模組中的 first()
函數,此模組隨附在 Perl 5.8 中。以下範例會找出第一個包含「Perl」的元素。
use List::Util qw(first);
my $element = first { /Perl/ } @array;
如果你無法使用 List::Util,你可以自己建立迴圈來執行相同的工作。一旦你找到元素,就使用 last 來停止迴圈。
my $found;
foreach ( @array ) {
if( /Perl/ ) { $found = $_; last }
}
如果你想要陣列索引,請使用 List::MoreUtils
中的 firstidx()
函數
use List::MoreUtils qw(firstidx);
my $index = firstidx { /Perl/ } @array;
或自行撰寫,逐一迭代索引並檢查陣列元素,直到找到符合條件的元素
my( $found, $index ) = ( undef, -1 );
for( $i = 0; $i < @array; $i++ ) {
if( $array[$i] =~ /Perl/ ) {
$found = $array[$i];
$index = $i;
last;
}
}
(由 brian d foy 貢獻)
Perl 陣列沒有固定大小,因此如果您只想新增或移除項目,就不需要連結串列。您可以使用陣列運算,例如 push
、pop
、shift
、unshift
或 splice
來執行此操作。
不過,在某些情況下,連結串列可能會很有用,例如您想要「分片」陣列,以便擁有許多小陣列,而不是單一大陣列。您可以保留比 Perl 最大陣列索引更長的陣列,在執行緒程式中分別鎖定較小的陣列,重新配置較少的記憶體,或快速在鏈的中央插入元素。
Steve Lembark 在他的 YAPC::NA 2009 演講「Perly 連結串列」中詳細說明了這些細節 ( http://www.slideshare.net/lembark/perly-linked-lists ),儘管您只需使用他的 LinkedList::Single 模組即可。
(由 brian d foy 貢獻)
如果您想要無限循環陣列,您可以將索引遞增陣列中元素數量的模數
my @array = qw( a b c );
my $i = 0;
while( 1 ) {
print $array[ $i++ % @array ], "\n";
last if $i > 20;
}
您也可以使用 Tie::Cycle 來使用一個標量,該標量始終具有循環陣列的下一元素
use Tie::Cycle;
tie my $cycle, 'Tie::Cycle', [ qw( FFFFFF 000000 FFFF00 ) ];
print $cycle; # FFFFFF
print $cycle; # 000000
print $cycle; # FFFF00
Array::Iterator::Circular 為循環陣列建立一個迭代器物件
use Array::Iterator::Circular;
my $color_iterator = Array::Iterator::Circular->new(
qw(red green blue orange)
);
foreach ( 1 .. 20 ) {
print $color_iterator->next, "\n";
}
如果您已安裝 Perl 5.8.0 或更新版本,或者已安裝 Scalar-List-Utils 1.03 或更新版本,您可以說
use List::Util 'shuffle';
@shuffled = shuffle(@list);
如果不是,您可以使用 Fisher-Yates 洗牌法。
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
return unless @$deck; # must not be empty!
my $i = @$deck;
while (--$i) {
my $j = int rand ($i+1);
@$deck[$i,$j] = @$deck[$j,$i];
}
}
# shuffle my mpeg collection
#
my @mpeg = <audio/*/*.mp3>;
fisher_yates_shuffle( \@mpeg ); # randomize @mpeg in place
print @mpeg;
請注意,上述實作會就地洗牌陣列,這與 List::Util::shuffle()
不同,後者會取得一個串列並傳回一個新的洗牌串列。
您可能看過使用 splice 運作的洗牌演算法,隨機挑選另一個元素與目前的元素交換
srand;
@new = ();
@old = 1 .. 10; # just a demo
while (@old) {
push(@new, splice(@old, rand @old, 1));
}
這是很糟糕的,因為 splice 已經是 O(N),而且由於您執行 N 次,您剛剛發明了一個二次演算法;也就是說,O(N**2)。這無法擴充,儘管 Perl 非常有效率,以至於在您有相當大的陣列之前,您可能不會注意到這一點。
使用 for
/foreach
for (@lines) {
s/foo/bar/; # change that word
tr/XZ/ZX/; # swap those letters
}
這是另一個;讓我們計算球體體積
my @volumes = @radii;
for (@volumes) { # @volumes has changed parts
$_ **= 3;
$_ *= (4/3) * 3.14159; # this will be constant folded
}
也可以使用 map()
來完成,它用於將一個串列轉換成另一個串列
my @volumes = map {$_ ** 3 * (4/3) * 3.14159} @radii;
如果您想要對雜湊值做同樣的事情來修改值,可以使用 values
函式。從 Perl 5.6 開始,值不會被複製,因此如果您修改 $orbit (在此情況下),您會修改值。
for my $orbit ( values %orbits ) {
($orbit **= 3) *= (4/3) * 3.14159;
}
在 perl 5.6 之前,values
會傳回值的複本,因此較舊的 perl 程式碼通常包含諸如 @orbits{keys %orbits}
之類的結構,而不是 values %orbits
,其中雜湊會被修改。
使用 rand()
函式(請參閱 "rand" in perlfunc)
my $index = rand @array;
my $element = $array[$index];
或者,只需
my $element = $array[ rand @array ];
使用 CPAN 上的 List::Permutor 模組。如果清單實際上是一個陣列,請嘗試 Algorithm::Permute 模組(也在 CPAN 上)。它是用 XS 程式碼編寫的,非常有效率
use Algorithm::Permute;
my @array = 'a'..'d';
my $p_iterator = Algorithm::Permute->new ( \@array );
while (my @perm = $p_iterator->next) {
print "next permutation: (@perm)\n";
}
為了執行得更快,你可以執行
use Algorithm::Permute;
my @array = 'a'..'d';
Algorithm::Permute::permute {
print "next permutation: (@array)\n";
} @array;
以下是一個小程式,它會產生輸入中每一行所有單字的所有排列。permute()
函式中體現的演算法在 Knuth 的《電腦程式設計的藝術》第 4 卷(尚未出版)中討論,並且會在任何清單上執行
#!/usr/bin/perl -n
# Fischer-Krause ordered permutation generator
sub permute (&@) {
my $code = shift;
my @idx = 0..$#_;
while ( $code->(@_[@idx]) ) {
my $p = $#idx;
--$p while $idx[$p-1] > $idx[$p];
my $q = $p or return;
push @idx, reverse splice @idx, $p;
++$q while $idx[$p-1] > $idx[$q];
@idx[$p-1,$q]=@idx[$q,$p-1];
}
}
permute { print "@_\n" } split;
Algorithm::Loops 模組也提供 NextPermute
和 NextPermuteNum
函式,這些函式可以有效率地找出陣列的所有唯一排列,即使它包含重複值,也會就地修改:如果其元素以反向排序順序排列,則陣列會被反轉,使其排序,並傳回 false;否則會傳回下一個排列。
NextPermute
使用字串順序,而 NextPermuteNum
使用數字順序,因此你可以像這樣列舉 0..9
的所有排列
use Algorithm::Loops qw(NextPermuteNum);
my @list= 0..9;
do { print "@list\n" } while NextPermuteNum @list;
提供比較函式給 sort()(在 "sort" in perlfunc 中說明)
@list = sort { $a <=> $b } @list;
預設的排序函式是 cmp,字串比較,它會將 (1, 2, 10)
排序成 (1, 10, 2)
。<=>
,如上所用,是數字比較運算子。
如果你有一個複雜的函式需要取出你想要排序的部分,那麼不要在排序函式內執行。先將它取出,因為排序區塊可以針對同一個元素呼叫多次。以下是如何取出每個項目第一個數字後的第一個單字,然後對這些單字進行不分大小寫的排序。
my @idx;
for (@data) {
my $item;
($item) = /\d+\s*(\S+)/;
push @idx, uc($item);
}
my @sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ];
也可以用這種方式撰寫,使用一個被稱為 Schwartzian Transform 的技巧
my @sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data;
如果你需要根據多個欄位排序,下列範例很有用。
my @sorted = sort {
field1($a) <=> field1($b) ||
field2($a) cmp field2($b) ||
field3($a) cmp field3($b)
} @data;
這可以方便地與如上所述的預先計算金鑰結合。
請參閱 http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz 中「Far More Than You Ever Wanted To Know」系列中的 sort 文章,以進一步了解此方法。
另請參閱 perlfaq4 中稍後關於排序雜湊的問題。
使用 pack()
和 unpack()
,或使用 vec()
和位元運算。
例如,您不必將個別位元儲存在陣列中(這表示您會浪費許多空間)。若要將位元陣列轉換為字串,請使用 vec()
設定正確的位元。這會設定 $vec
,僅當 $ints[N]
已設定時才會設定位元 N
my @ints = (...); # array of bits, e.g. ( 1, 0, 0, 1, 1, 0 ... )
my $vec = '';
foreach( 0 .. $#ints ) {
vec($vec,$_,1) = 1 if $ints[$_];
}
字串 $vec
僅佔用所需的位元數。例如,如果您在 @ints
中有 16 個項目,則 $vec
僅需要兩個位元組來儲存它們(不計入純量變數的開銷)。
以下是取得 $vec
中的向量,並將這些位元放入 @ints
陣列的方法
sub bitvec_to_list {
my $vec = shift;
my @ints;
# Find null-byte density then select best algorithm
if ($vec =~ tr/\0// / length $vec > 0.95) {
use integer;
my $i;
# This method is faster with mostly null-bytes
while($vec =~ /[^\0]/g ) {
$i = -9 + 8 * pos $vec;
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
push @ints, $i if vec($vec, ++$i, 1);
}
}
else {
# This method is a fast general algorithm
use integer;
my $bits = unpack "b*", $vec;
push @ints, 0 if $bits =~ s/^(\d)// && $1;
push @ints, pos $bits while($bits =~ /1/g);
}
return \@ints;
}
位元向量越稀疏,此方法會越快。(感謝 Tim Bunce 和 Winfried Koenig 提供。)
您可以使用 Benjamin Goldberg 的建議,讓 while 迴圈短很多
while($vec =~ /[^\0]+/g ) {
push @ints, grep vec($vec, $_, 1), $-[0] * 8 .. $+[0] * 8;
}
或使用 CPAN 模組 Bit::Vector
my $vector = Bit::Vector->new($num_of_bits);
$vector->Index_List_Store(@ints);
my @ints = $vector->Index_List_Read();
Bit::Vector 提供有效率的方法,用於位元向量、小型整數集合和「大整數」運算。
以下是使用 vec() 的更詳細說明
# vec demo
my $vector = "\xff\x0f\xef\xfe";
print "Ilya's string \\xff\\x0f\\xef\\xfe represents the number ",
unpack("N", $vector), "\n";
my $is_set = vec($vector, 23, 1);
print "Its 23rd bit is ", $is_set ? "set" : "clear", ".\n";
pvec($vector);
set_vec(1,1,1);
set_vec(3,1,1);
set_vec(23,1,1);
set_vec(3,1,3);
set_vec(3,2,3);
set_vec(3,4,3);
set_vec(3,4,7);
set_vec(3,8,3);
set_vec(3,8,7);
set_vec(0,32,17);
set_vec(1,32,17);
sub set_vec {
my ($offset, $width, $value) = @_;
my $vector = '';
vec($vector, $offset, $width) = $value;
print "offset=$offset width=$width value=$value\n";
pvec($vector);
}
sub pvec {
my $vector = shift;
my $bits = unpack("b*", $vector);
my $i = 0;
my $BASE = 8;
print "vector length in bytes: ", length($vector), "\n";
@bytes = unpack("A8" x length($vector), $bits);
print "bits are: @bytes\n\n";
}
簡而言之,您可能只應在純量或函式上使用 defined,而不要在集合(陣列和雜湊)上使用。請參閱 Perl 5.004 版本或更新版本的 "defined" in perlfunc,以取得更多詳細資料。
(由 brian d foy 貢獻)
有幾種方法可以處理整個雜湊。您可以取得金鑰清單,然後逐一檢視每個金鑰,或一次取得一組金鑰值。
若要檢視所有金鑰,請使用 keys
函式。這會擷取雜湊的所有金鑰,並以清單的形式傳回給您。然後,您可以透過您正在處理的特定金鑰取得值
foreach my $key ( keys %hash ) {
my $value = $hash{$key}
...
}
取得金鑰清單後,您可以在處理雜湊元素之前處理該清單。例如,您可以對金鑰排序,以便以字典順序處理它們
foreach my $key ( sort keys %hash ) {
my $value = $hash{$key}
...
}
或者,您可能只想處理部分項目。如果您只想處理以 text:
開頭的金鑰,您可以使用 grep
選取它們
foreach my $key ( grep /^text:/, keys %hash ) {
my $value = $hash{$key}
...
}
如果雜湊非常大,您可能不想建立長的金鑰清單。若要節省一些記憶體,您可以使用 each()
一次取得一組金鑰值,它會傳回您尚未看過的組
while( my( $key, $value ) = each( %hash ) ) {
...
}
each
算子會以看似隨機的順序傳回成對元素,因此如果順序對你很重要,你必須堅持使用 keys
方法。
不過,each()
算子可能會有點棘手。在使用時,你不能新增或刪除雜湊的鍵,否則可能會跳過或重新處理某些成對元素,因為 Perl 在內部會對所有元素重新雜湊。此外,雜湊只有一個反覆運算器,因此如果你在同一個雜湊上混用 keys
、values
或 each
,你可能會重設反覆運算器並搞亂你的處理程序。請參閱 perlfunc 中的 each
條目以取得更多詳細資訊。
(由 brian d foy 貢獻)
在決定合併兩個雜湊之前,你必須決定如果兩個雜湊都包含相同的鍵,以及是否要保留原始雜湊不變,該怎麼辦。
如果你要保留原始雜湊,請將一個雜湊(%hash1
)複製到一個新的雜湊(%new_hash
),然後將另一個雜湊(%hash2
)的鍵新增到新的雜湊。檢查鍵是否已存在於 %new_hash
中,讓你得以決定如何處理重複項
my %new_hash = %hash1; # make a copy; leave %hash1 alone
foreach my $key2 ( keys %hash2 ) {
if( exists $new_hash{$key2} ) {
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else {
$new_hash{$key2} = $hash2{$key2};
}
}
如果你不想建立新的雜湊,你仍然可以使用這種迴圈技巧;只要將 %new_hash
變更為 %hash1
即可。
foreach my $key2 ( keys %hash2 ) {
if( exists $hash1{$key2} ) {
warn "Key [$key2] is in both hashes!";
# handle the duplicate (perhaps only warning)
...
next;
}
else {
$hash1{$key2} = $hash2{$key2};
}
}
如果你不在乎一個雜湊會覆寫另一個雜湊的鍵和值,你可以直接使用雜湊切片將一個雜湊新增到另一個雜湊。在這種情況下,當 %hash2
和 %hash1
有相同的鍵時,%hash2
的值會取代 %hash1
的值
@hash1{ keys %hash2 } = values %hash2;
(由 brian d foy 貢獻)
簡單的答案是「不要這麼做!」
如果你使用 each() 反覆運算雜湊,你可以刪除最近傳回的鍵,不用擔心它。如果你刪除或新增其他鍵,反覆運算器可能會跳過或重複它們,因為 Perl 可能重新排列雜湊表。請參閱 perlfunc 中的 each()
條目。
建立反向雜湊
my %by_value = reverse %by_key;
my $key = $by_value{$value};
這並不是很有效率。使用以下方法會更省空間
while (my ($key, $value) = each %by_key) {
$by_value{$value} = $key;
}
如果你的雜湊可能包含重複值,以上方法只會找到一個關聯鍵。這可能會讓你擔心,也可能不會。如果你真的擔心,你可以隨時將雜湊反向成陣列雜湊
while (my ($key, $value) = each %by_key) {
push @{$key_list_by_value{$value}}, $key;
}
(由 brian d foy 貢獻)
這與 perlfaq4 中的「如何處理整個雜湊?」非常類似,但對於常見情況來說,它更簡單一點。
你可以在純量內容中使用內建函式 keys()
來找出雜湊中有多少條目
my $key_count = keys %hash; # must be scalar context!
如果您想要找出有多少個項目具有已定義的值,那有點不同。您必須檢查每個值。grep
很方便
my $defined_value_count = grep { defined } values %hash;
您可以使用相同的結構以任何您喜歡的方式計算項目。如果您想要包含元音的鍵的計數,您只需對此進行測試
my $vowel_count = grep { /[aeiou]/ } keys %hash;
標量上下文的 grep
會傳回計數。如果您想要匹配項目的清單,請改用清單上下文
my @defined_values = grep { defined } values %hash;
keys()
函數也會重設反覆運算器,這表示如果您在使用其他雜湊運算子(例如 each()
)之間使用此函數,您可能會看到奇怪的結果。
(由 brian d foy 貢獻)
若要對雜湊進行排序,請從鍵開始。在此範例中,我們將鍵清單提供給排序函數,然後該函數會以 ASCII 順序比較它們(可能會受到您的區域設定影響)。輸出清單中的鍵會以 ASCII 順序排列。一旦我們有了鍵,我們就可以瀏覽它們來建立報告,其中會以 ASCII 順序列出鍵。
my @keys = sort { $a cmp $b } keys %hash;
foreach my $key ( @keys ) {
printf "%-20s %6d\n", $key, $hash{$key};
}
不過,我們可以在 sort()
區塊中變得更花俏。我們可以計算它們的值並使用該值作為比較,而不是比較鍵。
例如,為了使我們的報告順序不區分大小寫,我們在比較鍵之前使用 lc
將其轉換為小寫
my @keys = sort { lc $a cmp lc $b } keys %hash;
注意:如果計算很昂貴或雜湊有許多元素,您可能需要查看 Schwartzian Transform 來快取計算結果。
如果我們想要按雜湊值進行排序,我們使用雜湊鍵來查詢它。我們仍然會得到一個鍵清單,但這次它們會按其值排序。
my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;
從那裡,我們可以變得更複雜。如果雜湊值相同,我們可以在雜湊鍵上提供次要排序。
my @keys = sort {
$hash{$a} <=> $hash{$b}
or
"\L$a" cmp "\L$b"
} keys %hash;
您可以考慮使用 DB_File
模組和 tie()
,使用 $DB_BTREE
雜湊繫結,如 "In Memory Databases" in DB_File 中所述。CPAN 中的 Tie::IxHash 模組也可能具有指導意義。儘管這確實讓您的雜湊保持排序,但您可能不喜歡從繫結介面遭受的減速。您確定需要這樣做嗎? :)
雜湊包含標量的成對:第一個是鍵,第二個是值。鍵將被強制轉換為字串,儘管值可以是任何類型的標量:字串、數字或參考。如果雜湊 %hash 中存在鍵 $key
,則 exists($hash{$key})
將傳回 true。給定鍵的值可以是 undef
,在這種情況下 $hash{$key}
將為 undef
,而 exists $hash{$key}
將傳回 true。這對應於雜湊中存在 ($key
, undef
)。
圖片有助於理解... 這是 %hash
表格
keys values
+------+------+
| a | 3 |
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
並符合下列條件
$hash{'a'} is true
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is true
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true
如果你現在說
undef $hash{'a'}
你的表格現在讀取
keys values
+------+------+
| a | undef|
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
並符合下列條件;大寫字母表示變更
$hash{'a'} is FALSE
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is FALSE
exists $hash{'a'} is true (Perl 5 only)
grep ($_ eq 'a', keys %hash) is true
注意最後兩個:你有一個未定義的值,但有一個已定義的鍵!
現在,考慮這一點
delete $hash{'a'}
你的表格現在讀取
keys values
+------+------+
| x | 7 |
| d | 0 |
| e | 2 |
+------+------+
並符合下列條件;大寫字母表示變更
$hash{'a'} is false
$hash{'d'} is false
defined $hash{'d'} is true
defined $hash{'a'} is false
exists $hash{'a'} is FALSE (Perl 5 only)
grep ($_ eq 'a', keys %hash) is FALSE
看,整個條目消失了!
這取決於繫結雜湊的 EXISTS() 實作。例如,繫結到 DBM* 檔案的雜湊沒有未定義的概念。這也表示 exists() 和 defined() 對 DBM* 檔案執行相同的工作,而它們最終執行的動作與對一般雜湊執行的動作不同。
(由 brian d foy 貢獻)
你可以使用 keys
或 values
函數來重設 each
。若要只重設 each
使用的迭代器,而不執行其他動作,請在無效的內容中使用其中一個
keys %hash; # resets iterator, nothing else.
values %hash; # resets iterator, nothing else.
請參閱 perlfunc 中 each
的文件。
首先,你將雜湊的鍵萃取到清單中,然後解決上述所述的「移除重複」問題。例如
my %seen = ();
for my $element (keys(%foo), keys(%bar)) {
$seen{$element}++;
}
my @uniq = keys %seen;
或更簡潔地說
my @uniq = keys %{{%foo,%bar}};
或如果你真的想節省空間
my %seen = ();
while (defined ($key = each %foo)) {
$seen{$key}++;
}
while (defined ($key = each %bar)) {
$seen{$key}++;
}
my @uniq = keys %seen;
自行將結構字串化(一點都不好玩),或從 CPAN 取得 MLDBM(使用 Data::Dumper)模組,並將其置於 DB_File 或 GDBM_File 之上。你也可以嘗試 DBM::Deep,但它可能會有點慢。
使用 CPAN 中的 Tie::IxHash。
use Tie::IxHash;
tie my %myhash, 'Tie::IxHash';
for (my $i=0; $i<20; $i++) {
$myhash{$i} = 2*$i;
}
my @keys = keys %myhash;
# @keys = (0,1,2,3,...)
(由 brian d foy 貢獻)
您使用的是非常舊版的 Perl 嗎?
通常,存取不存在的雜湊鍵值不會建立該鍵。
my %hash = ();
my $value = $hash{ 'foo' };
print "This won't print\n" if exists $hash{ 'foo' };
不過,傳遞 $hash{ 'foo' }
給子常式曾經是一個特例。由於您可以直接指定給 $_[0]
,因此 Perl 必須準備好進行該指定,所以它會預先建立雜湊鍵
my_sub( $hash{ 'foo' } );
print "This will print before 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
# $_[0] = 'bar'; # create hash key in case you do this
1;
}
然而,自 Perl 5.004 起,這個情況變成一個特例,而 Perl 只有在您進行指定時才會建立雜湊鍵
my_sub( $hash{ 'foo' } );
print "This will print, even after 5.004\n" if exists $hash{ 'foo' };
sub my_sub {
$_[0] = 'bar';
}
不過,如果您想要舊有的行為(並仔細考慮這一點,因為這是一個奇怪的副作用),您可以傳遞一個雜湊切片。Perl 5.004 沒有將這設為特例
my_sub( @hash{ qw/foo/ } );
通常是雜湊參照,也許像這樣
$record = {
NAME => "Jason",
EMPNO => 132,
TITLE => "deputy peon",
AGE => 23,
SALARY => 37_000,
PALS => [ "Norbert", "Rhys", "Phineas"],
};
參照文件在 perlref 和 perlreftut 中。複雜資料結構的範例在 perldsc 和 perllol 中。結構和物件導向類別的範例在 perlootut 中。
(由 brian d foy 和 Ben Morrow 提供)
雜湊鍵是字串,因此您無法真的使用參照作為鍵。當您嘗試這樣做時,perl 會將參照轉換成它的字串化形式(例如,HASH(0xDEADBEEF)
)。從那裡,您無法從字串化形式取得參照,至少無法在不自行執行一些額外工作的情況下取得。
請記住,即使被參照的變數超出範圍,雜湊中的項目仍然會存在,而且 Perl 完全有可能隨後在同一個位址配置一個不同的變數。這表示一個新的變數可能會意外地與舊值的數值關聯在一起。
如果您有 Perl 5.10 或更新版本,而且您只是想將一個值儲存在參考中以供稍後查詢,您可以使用核心 Hash::Util::Fieldhash 模組。如果您使用多個執行緒(這會導致所有變數重新配置到新的位址,改變其字串化),這也會處理重新命名金鑰,以及在被參考的變數超出範圍時,進行垃圾回收。
如果您實際上需要能夠從每個雜湊項目中取得一個真實的參考,您可以使用 Tie::RefHash 模組,它會為您執行所需的工作。
(由 brian d foy 貢獻)
這個問題的訣竅是避免意外的自動化。如果您想檢查三個金鑰的深度,您可能會天真地嘗試這樣做
my %hash;
if( exists $hash{key1}{key2}{key3} ) {
...;
}
即使您從一個完全空的雜湊開始,在呼叫 exists
之後,您已經建立了檢查 key3
所需的結構
%hash = (
'key1' => {
'key2' => {}
}
);
那是自動化。您可以透過幾種方式來解決這個問題。最簡單的方法就是關閉它。CPAN 上提供了詞法 autovivification
實用程式。現在您不會新增到雜湊
{
no autovivification;
my %hash;
if( exists $hash{key1}{key2}{key3} ) {
...;
}
}
CPAN 上的 Data::Diver 模組也可以為您執行此操作。它的 Dive
子常式不僅可以告訴您金鑰是否存在,還可以取得值
use Data::Diver qw(Dive);
my @exists = Dive( \%hash, qw(key1 key2 key3) );
if( ! @exists ) {
...; # keys do not exist
}
elsif( ! defined $exists[0] ) {
...; # keys exist but value is undef
}
您也可以輕鬆地自己執行此操作,方法是在移到下一層級之前,檢查雜湊的每個層級。這基本上是 Data::Diver 為您執行的操作
if( check_hash( \%hash, qw(key1 key2 key3) ) ) {
...;
}
sub check_hash {
my( $hash, @keys ) = @_;
return unless @keys;
foreach my $key ( @keys ) {
return unless eval { exists $hash->{$key} };
$hash = $hash->{$key};
}
return 1;
}
自版本 5.8.0 以來,雜湊可以限制為固定數量的給定金鑰。建立和處理受限雜湊的方法是由 Hash::Util 模組匯出的。
Perl 是二進位乾淨的,因此它可以很好地處理二進位資料。然而,在 Windows 或 DOS 上,您必須對二進位檔案使用 binmode
,以避免換行符號的轉換。一般來說,您應該在任何時候都使用 binmode
來處理二進位資料。
另請參閱 perlfunc 中的「binmode」 或 perlopentut。
如果您擔心 8 位元文字資料,請參閱 perllocale。不過,如果您想要處理多位元元字元,則有一些陷阱。請參閱正規表示式的章節。
假設您不關心「NaN」或「Infinity」等 IEEE 表示法,您可能只想使用正規表示式(另請參閱 perlretut 和 perlre)
use 5.010;
if ( /\D/ )
{ say "\thas nondigits"; }
if ( /^\d+\z/ )
{ say "\tis a whole number"; }
if ( /^-?\d+\z/ )
{ say "\tis an integer"; }
if ( /^[+-]?\d+\z/ )
{ say "\tis a +/- integer"; }
if ( /^-?(?:\d+\.?|\.\d)\d*\z/ )
{ say "\tis a real number"; }
if ( /^[+-]?(?=\.?\d)\d*\.?\d*(?:e[+-]?\d+)?\z/i )
{ say "\tis a C float" }
還有一些常用的模組可供執行此任務。 Scalar::Util(隨 5.8 版發行)提供對 Perl 內部函式 looks_like_number
的存取,用於判斷變數是否看起來像數字。 Data::Types 匯出函式,使用上述和其它正規表示式來驗證資料類型。第三個是 Regexp::Common,它有正規表示式來比對各種數字類型。這三個模組都可以在 CPAN 取得。
如果您使用的是 POSIX 系統,Perl 支援 POSIX::strtod
函式,用於將字串轉換為雙精度浮點數(以及 POSIX::strtol
,用於長整數)。它的語意有點繁瑣,因此這裡有一個 getnum
封裝函式,提供更方便的存取。此函式會接收一個字串並傳回它找到的數字,或傳回 undef
,表示輸入不是 C 浮點數。如果您只想說「這是浮點數嗎?」,is_numeric
函式是 getnum
的前端。
sub getnum {
use POSIX qw(strtod);
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
$! = 0;
my($num, $unparsed) = strtod($str);
if (($str eq '') || ($unparsed != 0) || $!) {
return undef;
}
else {
return $num;
}
}
sub is_numeric { defined getnum($_[0]) }
或者,您也可以查看 CPAN 上的 String::Scanf 模組。
對於某些特定應用程式,您可以使用其中一個 DBM 模組。請參閱 AnyDBM_File。更一般來說,您應該參考 CPAN 中的 FreezeThaw 或 Storable 模組。從 Perl 5.8 開始,Storable 成為標準發行的一部分。以下是一個使用 Storable 的 store
和 retrieve
函式的範例
use Storable;
store(\%hash, "filename");
# later on...
$href = retrieve("filename"); # by ref
%hash = %{ retrieve("filename") }; # direct to hash
CPAN 上的 Data::Dumper 模組(或 Perl 的 5.005 版)非常適合列印資料結構。CPAN 上的 Storable 模組(或 Perl 的 5.8 版)提供一個稱為 dclone
的函式,用於遞迴複製其引數。
use Storable qw(dclone);
$r2 = dclone($r1);
其中 $r1
可以是您想要的任何資料結構的參考。它將被深度複製。由於 dclone
會接收和傳回參考,因此如果您有一個想要複製的陣列雜湊,您必須加上額外的標點符號。
%newhash = %{ dclone(\%oldhash) };
(由 Ben Morrow 提供)
您可以使用 UNIVERSAL
類別(請參閱 UNIVERSAL)。但是,請務必仔細考量這樣做的後果:為每個物件新增方法很可能會產生意想不到的後果。如果可能的話,最好讓所有物件繼承自某個共用基底類別,或使用支援角色的物件系統,例如 Moose。
從 CPAN 取得 Business::CreditCard 模組。
CPAN 上 PGPLOT 模組中的 arrays.h/arrays.c 程式碼就是這樣做的。如果您正在進行大量的浮點數或雙精度浮點數處理,請考慮改用 CPAN 上的 PDL 模組——它讓數字運算變得容易。
請參閱 https://metacpan.org/release/PGPLOT 以取得程式碼。
版權所有 (c) 1997-2010 Tom Christiansen、Nathan Torkington 和其他註明的作者。保留所有權利。
此文件是免費的文件;您可以在與 Perl 相同的條款下重新散佈或修改它。
無論其散佈方式為何,此檔案中的所有程式碼範例特此置於公有領域。您可以在自己的程式中使用此程式碼,以供娛樂或營利,視您的需要而定。在程式碼中加上簡單的註解以表示出處會是很客氣的行為,但並非必要。