perlfaq4 - 数据处理
版本 5.20210520
本部分常见问题解答回答了与处理数字、日期、字符串、数组、哈希和各种数据问题相关的问题。
有关详细说明,请参阅大卫·戈德伯格的“每个计算机科学家都应该了解的浮点运算”(http://web.cse.msu.edu/~cse320/Documents/FloatingPoint.pdf)。
在内部,您的计算机以二进制形式表示浮点数。数字(如 2 的幂)计算机无法准确存储所有数字。某些实数在此过程中会丢失精度。这是计算机存储数字的方式存在的问题,它会影响所有计算机语言,而不仅仅是 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 命令同名,该命令在命令行中使用八进制数字作为参数。在此示例中,命令行上的 chmod
知道其第一个参数是八进制数,因为这是它的作用
%prompt> chmod 644 file
如果您想在 Perl 中使用相同的字面数字 (644),则必须通过在数字前加上 0
或使用 oct
告诉 Perl 将它们视为八进制数字
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(在 32 位计算机上)以下的 Perl 数字将非常像数学整数。其他数字无法保证。
与 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();
剩余的转换(例如,hex -> oct、bin -> hex 等)留给有兴趣的读者练习。
二进制算术运算符的行为取决于它们是用于数字还是字符串。运算符将字符串视为一系列位并使用该字符串(字符串 "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
——你会让你的数字变得不那么随机,而不是更随机。
计算机擅长可预测,不擅长随机(尽管你的程序中的错误会造成相反的假象 :-)。http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz 中“远远超出你想要了解的”集合中的随机文章,由 Tom Phoenix 提供,对此进行了更多讨论。约翰·冯·诺依曼说,“任何试图通过确定性手段生成随机数的人,当然处于一种罪恶状态。”
Perl 依赖于底层系统来实现 rand
和 srand
;在某些系统上,生成的数字不够随机(尤其是在 Windows 上:参见 http://www.perlmonks.org/?node_id=803632)。Math
命名空间中的几个 CPAN 模块实现了更好的伪随机生成器;例如,参见 Math::Random::MT(“梅森旋转器”,速度快)或 Math::TrulyRandom(利用系统计时器的不完善来生成随机数,速度相当慢)。更多生成随机数的算法在 http://www.nr.com/ 上的“C 语言中的数值食谱”中进行了描述
要获取两个值之间的随机数,可以使用 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 贡献)
你可以将所有日期存储为数字,然后进行减法。但生活并不总是那么简单。
Perl 附带的 Time::Piece 模块用一个返回对象的版本替换了 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";
大多数人尝试使用时间而不是日历来确定日期,但这假设每一天都是 24 小时。对于大多数人来说,一年中有两天不是这样:切换到夏令时和从夏令时切换回来会打乱这一点。例如,其他建议有时会出错
从 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。
这取决于您所说的“转义”的含义。URL 转义在 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";
}
}
它打印出:“第三条鱼是红色的。”您还可以使用重复计数和重复模式,如下所示
/(?:\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"
由于引号的限制,这是一个相当复杂的问题。谢天谢地,我们有《精通正则表达式》的作者 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 文档。检查以下三件事
如果您想缩进此处文档中的文本,可以执行以下操作
# 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--/;
下面是一个适用于缩进此处文档的通用修复程序函数。它希望使用此处文档作为其参数进行调用。它会查看每行是否以一个通用子字符串开头,如果是,则会去掉该子字符串。否则,它会获取第一行上找到的前导空格量,并从每后续行中删除该空格量。
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 开始,该语言中添加了一种编写缩进此处文档的更简单、更简洁的方法:波浪号 (~) 修饰符。有关详细信息,请参阅perlop 中的“缩进此处文档”。
(由 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 中的项目(如果没有项目,则返回未定义)。如果您打算从数组中获取恰好一个元素,则应使用此表单。
@array[1]
是数组切片,尽管它只有一个索引。您可以通过指定其他索引作为列表(如@array[1,4,3,0]
)同时提取多个元素。
在赋值的左侧使用切片会向右侧提供列表上下文。这可能导致意外结果。例如,如果您想从文件句柄中读取一行,则分配给标量值就可以了
$array[1] = <STDIN>;
但是,在列表上下文中,行输入运算符将所有行作为列表返回。第一行进入@array[1]
,其余行神秘地消失了
@array[1] = <STDIN>; # most likely not what you want
use warnings
pragma 或-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
并增加该键的值。循环下次看到该元素时,其键存在于哈希中且该键的值为真(因为它不是 0 或 undef
),因此 next 跳过该迭代,循环转到下一个元素。
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
为真。
这些方法保证了快速的个体测试,但需要重新组织原始列表或数组。只有当您必须针对同一数组测试多个值时,它们才有效。
如果您只测试一次,标准模块 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 中但不在两者中同时存在的元素。把它想象成异或运算。
以下代码适用于单级数组。它使用字符串比较,不区分已定义和未定义的空字符串。如果您有其他需求,请修改。
$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 Linked Lists”中详细介绍了这些细节( 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;
提供一个比较函数来排序(在 perlfunc 中的“sort” 中描述)
@list = sort { $a <=> $b } @list;
默认的排序函数是 cmp,字符串比较,它会将 (1, 2, 10)
排序为 (1, 10, 2)
。上面使用的 <=>
是数字比较运算符。
如果你有一个复杂函数需要提取你想要排序的部分,那么不要在排序函数中执行此操作。首先提取它,因为排序 BLOCK 可以针对同一个元素调用多次。以下是如何提取每个项目第一个数字之后的第一个单词,然后对这些单词进行不区分大小写的排序的示例。
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
哈希绑定,如 DB_File 中的“内存数据库” 中所述。CPAN 中的 Tie::IxHash 模块也可能具有指导意义。尽管这确实让你的哈希保持排序,但你可能不喜欢从 tie 接口遭受的减速。你确定需要这样做吗? :)
哈希包含标量对:第一个是键,第二个是值。键将被强制转换为字符串,尽管值可以是任何类型的标量:字符串、数字或引用。如果 %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
注意最后两个:你有一个 undef 值,但有一个已定义的键!
现在,考虑这个
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
使用的迭代器而不执行任何其他操作,请在 void 上下文中使用其中一个
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
包装函数,以便更方便地访问。此函数接受一个字符串并返回它找到的数字,或者对于不是 C 浮点数的输入返回 undef
。如果你只想说“这是浮点数吗?”,那么 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 本身相同的条款重新分发和/或修改它。
无论其分发情况如何,本文件中的所有代码示例在此特此置于公共领域。你可以根据自己的意愿在自己的程序中使用此代码以娱乐或盈利为目的。在代码中添加一个简单的注释来表示感谢将是一种礼貌,但不是必需的。