perl one-liners explained,是继《Famous Awk One-Liners Explained》和《Famous Sed One-Liners Explained》,后Peteris Krumin推出的又一个系列文章,并已经出书了。本blog会陆续对其进行翻译成中文,以便大家学习。本篇是第三部分——数值计算
原文地址:http://www.catonmat.net/download/perl1line.txt
原书blog地址:http://www.catonmat.net/blog/perl-book/
本翻译项目地址:https://github.com/bollwarm/perlonelinecn
数值计算
21.检查一个数字是否是质数
1 |
perl -lne '(1x$_) !~ /^1?$|^(11+?)\1+$/ && print "$_ is prime"' |
这行代码巧妙使用一个正则表达式判断给予的数字是不是质数。不要想得太复杂了,我引用这个例子主要为了说明其他的技巧性。首先,这个数字通过“1x$_”被转变为他一元表达式。例如,5被转化了“1×5”,也就是“11111”。接着,对这个一元表达式进行用独特的正则表达式进行匹配,如果不匹配这个数字是个质数,否则他就是是个合数。
这个正则表达式由两部分组成:“^1?$”和“^(11+?)\1+$”。
第一个部分匹配“1”和空字符串。很明显,空字串和1不是质数,因此,这个表达式匹配了则表明这个数字不是一个质数。
第二部分检测是否两次或者更多次重复的1的组成了这个数字,正则匹配了,意味着数字由这样的多个1的重复组成的,否则的话,它是一个质数。
下面我们以5和6为例子,说明第二部分这个正则。
5的一元的表达式为“11111”。“(11+?)”首先会匹配两个1 “11”。后面引用“\1”变成“11”,于是整个正则表达式变成了“^11(11)+$”。这个不会匹配11111,于是匹配失败。由于正则部分使用了“+?”,它会回溯,接着匹配三个1“111”。\1于是变成了“111”这个正则表达式变成了“^111(111)+$”。这个也不会匹配。于是又重复用“1111”和“11111”匹配,当然也不会匹配。直到所有模式都尝试一遍都失败,因此这个数字是个质数。
数字6的一元表达式为“111111”。“(11+?)”首先匹配两个1 “11”,整个正则表达式为“^11(11)+$”。这个会匹配“111111”,所以数字不是一个质数。
“-lne”在第一部分和第二部分已经解释过,不再赘述。
22.打印一行中所有的域的和。
1 2 |
perl -MList::Util=sum -alne 'print sum @F' |
这行代码使用了一个域自动切割的命令行选项“-a”,并且通过“-MList::Util=sum”引入了“List::Util”模块的“sum”求和函数。List::Util”模块是Perl核心模块,不需要担心额外安装的问题。
作为一个自动分割的结果,分割的域保存在“@F”数组中,“sum”函数只不过对其求和而已。-Mmodule=arg选项引入了一个模块,相当于语句use module qw(arg),这行代码相当于: use List::Util qw(sum);while (<>) { @F = split(‘ ‘); print sum @F, “\n”;}
23.打印所有行所有域的值
1 2 |
perl -MList::Util=sum -alne 'push @S,@F; END { print sum @S }' |
这行代码通过push操作把每行分割的@F数组保存到@S数组中去,当所有行都输入后,perl退出前END{}块被执行,调用sum函数对@S的所有成员求和。这个和就是所有行所有域的和。
这个方案不是最好选择。它生成了一个额外的数组@S,最好的方式是是下面的:
1 2 |
perl -MList::Util=sum -alne '$s += sum @F; END { print $s }' |
24.以随机顺序显示每行的各个域。
1 2 |
perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"' |
本行和例22基本上相同,只不过本例使用shuffle函数将行域顺序随机打乱,并显示出来。
“@{[shuffle @F]}” 结构创建了一个指向“shuffle @F”内容的数组引用,并且通过“@ {…}”将其解引用成其内容。这是一个执行插入到引用中代码的常用技巧。这方法于我们需要把@F顺序打乱,并以空格分割输出是必须的。
另一种方法可以实现同样的功能,是通过join把@F的各个元素和空格连接起来,但是代码会更长一点:
1 2 |
perl -MList::Util=shuffle -alne 'print join " ", shuffle @F' |
25.找到每一行的最小的元素
1 2 |
perl -MList::Util=min -alne 'print min @F' |
本行代码使用了“List::Util”模块的“min”函数,形式和前面几例都相似。每行通过“-a”选项自动分割,并用“min”函数求出最小的元素并打印出来。
26.找到所有行中最小的元素。
1 2 |
perl -MList::Util=min -alne '@M = (@M, @F); END { print min @M }' |
这行代码是上一例子以及例23的整合。“@M=(@M,@F)”结构相当于“push @M,@F”,把@F的内容附加到数组@M中。
本代码需要把所有数据都存进内存。如果你用它去处理10T大的文件,他肯定会挂掉。因此,因此更好一点的方法是只把最小等数存进内存,最后输出:
1 |
perl -MList::Util=min -alne '$min = min @F; $rmin = $min unless defined $rmin && $min > $rmin; END { print $rmin }' |
代码会找出每一个行最小的一个并存进 $min,接着检查他是不是比当前最小数$rmin还小。最后打印当前最小数,保证了在所有的输人中他是最小的一个值。
27.找出一行中最大的数
1 2 |
perl -MList::Util=max -alne 'print max @F' |
和例25完全相同,只不过函数用最大函数“max”。
28.找出所有行中最小的一个
1 2 |
perl -MList::Util=max -alne '@M = (@M, @F); END { print max @M }' |
和例子26一样,也有另一种形式:
1 2 |
perl -MList::Util=max -alne '$max = max @F; $rmax = $max unless defined $rmax && $max < $rmax; END { print $rmax }' |
29.对所有的域的值用其绝对值代替
1 2 |
perl -alne 'print "@{[map { abs } @F]}"' |
本行代码,先通过“-a”命令行参数自动分割行的各个域,接着调用绝对值函数“abs”,通过map对其进行整体操作。最后利用匿名数组方式将各个域连接。@{ … }解释见例24.
30.找到每行域个数
1 2 |
perl -alne 'print scalar @F' |
本行代码强制@F到标量上下文,这在perl中就是@F的元素个数。因此输出结果就是每行的域个数。
31.输出每行,并在每行前面显示域数量。
1 2 |
perl -alne 'print scalar @F, " $_"' |
本例和例30基本上一样,除了把每行的内容“$_”也打印出来了。(提示,“-n”选项会导致进行输入循环,并把每行的内容存储在$_变量)。
32.计算所有行的域总数
1 2 |
perl -alne '$t += @F; END { print $t}' |
在这我们仅仅通过把每行的域的数量累加,并用变量“$t”保存,最后输出。结果就是所有域的总数。
33.打印出匹配模式的域总数。
1 2 |
perl -alne 'map { /regex/ && $t++ } @F; END { print $t }' |
这行代码使用map函数对@F数组的各个元素进行操作。本例中是检查其是否匹配模式/regex/,如果是的话,给变量$t加1。在最后END模块中打印出$t的值,即为匹配的域的总数。
还有一种比较好的方法是:
1 2 |
perl -alne '$t += /regex/ for @F; END { print $t }' |
每一个@F的元素用/regex/模式检验。如果匹配/regex/会返回值1(表示真),我们用+=操作符累加并赋值给$t.用这种方式使得匹配的数量会被保存在$t。 最好的方式是利用标量上下文的grep:
1 2 |
perl -alne '$t += grep /regex/, @F; END { print $t }' |
在标量上下文grep会返回匹配的数量,数量也用$t进行累加。
34.打印所有匹配模式的行总数
1 2 |
perl -lne '/regex/ && $t++; END { print $t }' |
如果当前行输出匹配正则模式,/regex/为true。/regex/ && $t++相当于if ($_ =~ /regex/) { $t++ },如果模式匹配,$t值就会加1.最后END模块输出$t值,就是匹配模式的行的数量。
35.打印PI的值到n位小数点后。
1 2 |
perl -Mbignum=bpi -le 'print bpi(21)' |
bignum库的bpi函数可以计算输出PI常量到所需足够精确地值。本例打印PI到小数点后20位。 bignum库也可以直接输出PI常量,这个数值保留39位的精确度:
1 2 |
perl -Mbignum=PI -le 'print PI' |
36.打印E的值到n为小数点后。
1 2 |
perl -Mbignum=bexp -le 'print bexp(1,21)' |
Bignum库也可以利用bexp函数,来计算和输出e及其n方幂的精确值,他的两个参数为幂值n以及精确位。本里打印了e的值(1一次方)到20位小数点后。
37.打印e的平方到31位精确值:
1 2 |
perl -Mbignum=bexp -le 'print bexp(2,31)' |
和PI一样,binum也支持输出常量e,值也保持39位的精确度。
1 2 |
perl -Mbignum=e -le 'print e' |
38.打印Unix 时间值(自从1970年一月一日, 00:00:00 UTC后累计秒的值)
1 2 |
perl -le 'print time' |
内建的函数“time”返回公元以来的秒数。
39.打印GMT(格林威治时间)和本机时间。
1 2 |
perl -le 'print scalar gmtime' |
“gmtime”函数是Perl内建的函数。如果使用标量上下文,他会打印格林威治时区(0时区)标准时间。
1 2 |
perl -le 'print scalar localtime' |
“localtime”内建函数,表现和“gmtime”大致一样,不过他显示的计算机本地的时间。 “gmtime”和“localtime”均返回9元素的列表(tm结构体),包含下述元素:
1 2 |
($second,[0]$minute,[1]$hour,[2]$month_day,[3]$month,[4]$year,[5]$week_day,[6]$year_day, [7]$is_daylight_saving[8]) |
根据实际的需要信息,你可以对此列表做切片,或者打印单个元素。例如,为了打印H:M:S,切割出localtime的2,1和0元素:
1 2 |
perl -le 'print join ":", (localtime)[2,1,0]' |
40. 打印昨天的日期
1 2 |
perl -MPOSIX -le '@now = localtime; $now[3] -= 1; print scalar localtime mktime @now' |
上例说了localtime返回一个9元素的各种时间项。其中第4项是当前日期数。如果我们对其减去1就得到昨天。“mktime”函数利用修改过的9项目列表构建一个Unix时间戳。最后“scalar localtime”打印了这个新的日期,就是昨天。
因为使用到了mktime函数,POSIX包是必须的,他应该是用于规范化负值。
41.打印14个月9天7秒前的日期
1 2 |
perl -MPOSIX -le '@now = localtime; $now[0] -= 7; $now[4] -= 14; $now[7] -= 9; print scalar localtime mktime @now' |
本行代码修改了@now列表的第1个,第5个以及第8个元素。第1个是秒数,第5个是月,第8个是日(见上面时间9元素表). 接着利用mktime生成把它转化成了Unix时间戳,再利用标量上下文的localtime打印日期:14个月9天7秒钟前。
42.计算阶乘。
1 2 |
perl -MMath::BigInt -le 'print Math::BigInt->new(5)->bfac()' |
本行代码使用Math::BigInt模块的bfac()函数,这个模块内置与Perl核心(不必另行安装)。
Math::BigInt->new(5)结构创建一个新的Math::BigInt对象,构造参数为5。接着用bfac()方法调用新创建的对象计算5的阶乘。如果计算其他数的阶乘,只需要在创建对象时候,更换构造参数的值为所希望的值即可。
另一种计算阶乘的方法是直接累乘从1到n的值:
1 2 |
perl -le '$f = 1; $f *= $_ for 1..5; print $f' |
我们给$f初始化赋值为1.接着做一个从1到5的循环,对$f进行各个值的累乘。结果是12345,这就是5的阶乘。
43.计算最大公约数。
1 2 |
perl -MMath::BigInt=bgcd -le 'print bgcd(@list_of_numbers)' |
Math::BigInt还内带有其他好多个非常有用的数学函数。这其中之一是bgcd,用来计算一列数字的最大公约数。
1 |
perl -MMath::BigInt=bgcd -le 'print bgcd(20,60,30)' |
当然,你也可以使用欧几里德算法(即辗转相除法)。给予两个数字$n和$m,这行代码找出两者的gcd。结果保存在$m中。
1 |
perl -le '$n = 20; $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $m' |
44.计算最小公倍数。
另外一个函数是lcm,计算最小公倍数,一下行代码计算(35,20,8)的最小公倍数。
1 2 |
perl -MMath::BigInt=blcm -le 'print blcm(35,20,8)' |
如果你了解一些数字定理,你应该能知道gcd和lcm之间是有联系的。给定两个数字$n和$m,他们的lcm等于$n*$m/gcd($n,$m),因此,也就是另一种计算最小公倍数的行代码:
1 2 |
perl -le '$a = $n = 20; $b = $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $a*$b/$m' |
45.生成5到15之间(包括15)的10个随机数
1 |
perl -le '$n=10; $min=5; $max=15; $, = " ";print map { int(rand($max-$min))+$min } 1..$n' |
你能通过改变$n,$min,$max调整此行代码。变量$n表示要生成多少个随机数,[$min,$max)表示生成数的访问。
变量$,设为空格,用来格式化输出的域间隔,默认没有设置。所以如果我们不设置它为空格,打印的数字将会连在一起。
46.找出列表的所有排列方式并打印出来。
1 2 |
perl -MAlgorithm::Permute -le '$l = [1,2,3,4,5]; $p = Algorithm::Permute->new($l); print @r while @r = $p->next' |
这行代码使用面向对象的接口Algorithm::Permute模块来找出排列(所有重新组合元素的方法)。
Algorithm::Permute的构造方法接受一个数组引用参数来排列。本例中元素为1,2,3,4,5。
对象方法的方法next返回下一个排列。循环调用这个方法可以获得所有可能的排列。
我们可以注意到很快就能输出大量的列表。对一个n个元素的列表来说有n!种排列。
另一种方法是使用permute子函数。
1 2 |
perl -MAlgorithm::Permute -le '@l = (1,2,3,4,5);Algorithm::Permute::permute { print "@l" } @l' |
47.生成幂集。
1 2 |
perl -MList::PowerSet=powerset -le '@l = (1,2,3,4,5); for (@{powerset(@l)}) { print "@$_" }' |
本例,我使用了CPAN的List::PowerSet模块。我输出powerset函数,本函数以一列表做为参数,返回一个引用,该应用为指向子集列表的引用的列表。 在for循环调用了powerset函数,传递给它@l的列表元素。接着,解引用返回powerset的值,这个值是一个指向子集列表的引用。最后,解引用每一个子集@$_并打印它。
对一个n元素的集合,他的幂集有2n个子集。
48.转换IP地址为无符号整数。
1 2 |
perl -le '$i=3; $u += ($_<<8*$i--) for "127.0.0.1" =~ /(\d+)/g; print $u' |
本行代码转换127.0.0.1的IP地址为无符号整数(他恰巧是2130706433)。
首先对IP地址做了个(\d+)全局匹配。做一个for循环做全局匹配的迭代,使所有数字得到匹配,匹配了IP地址的四个部分。 接着把匹配的数字加起来保存在$u中,其中第一部分位移83=24位,第二部分位移28=16位,第三部分8位,最后一部分直接加给$u。 但是本行代码没有对IP地址做任何的格式检验。你也可以使用一个更复杂一点正则表达式来检测,比如/^(\d+).(\d+).(\d+).(\d+)$/g。 经过和朋友一起讨论,我们想出另外一些方法:
1 2 |
perl -le '$ip="127.0.0.1"; $ip =~ s/(\d+)\.?/sprintf("%02x", $1)/ge; print hex($ip)' |
这个行代码巧妙地利用127.0.0.1可以被容易的转化为十六进程 7f000001的事实,再利用Perl的hex函数转化为十进制。
另一种方法是利用unpack:
1 2 |
perl -le 'print unpack("N", 127.0.0.1)' |
这个行代码大概是能达到最短的一个。它利用vstring语法(版本字符)表达IP地址。Vstring格式是指用特定顺序值组成的字符串。接着,利用网络字节顺序(Big-Endian顺序)新的格式化字符串被解包为一个数字,并把它打印出来。
如果有一个IP的字符串(不是一个vstring),你首先就要用inet_aton函数转化这个字符串为字节的形式:
1 2 |
perl -MSocket -le 'print unpack("N", inet_aton("127.0.0.1"))' |
这儿inet_aton函数转化字符串“127.0.0.1 ”为字节的形式(这恰恰和纯vstring 127.0.0.1是一样的),接着unpack它。
49.转化一个无符号整数为IP地址。
1 2 |
perl -MSocket -le 'print inet_ntoa(pack("N", 2130706433))' |
本例中整数2130706433首先被pack为一个Big-Endian数字,并且利用inet_ntoa函数转化这个数字为IP地址。
另外一种方法是用位移和逐字节打印的方式:
1 |
perl -le '$ip = 2130706433; print join ".", map { (($ip>>8*($_))&0xFF) } reverse 0..3' |
随便说一下,join “.”可以被特殊变量$,代替,它专门用来做为打印语句的默认分割符:
1 |
perl -le '$ip = 2130706433; $, = "."; print map { (($ip>>8*($_))&0xFF) } reverse 0..3' |