perl one-liners explained 中文版 (三)数值计算

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.检查一个数字是否是质数

这行代码巧妙使用一个正则表达式判断给予的数字是不是质数。不要想得太复杂了,我引用这个例子主要为了说明其他的技巧性。首先,这个数字通过“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.打印一行中所有的域的和。

这行代码使用了一个域自动切割的命令行选项“-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.打印所有行所有域的值

这行代码通过push操作把每行分割的@F数组保存到@S数组中去,当所有行都输入后,perl退出前END{}块被执行,调用sum函数对@S的所有成员求和。这个和就是所有行所有域的和。

这个方案不是最好选择。它生成了一个额外的数组@S,最好的方式是是下面的:

24.以随机顺序显示每行的各个域。

本行和例22基本上相同,只不过本例使用shuffle函数将行域顺序随机打乱,并显示出来。

“@{[shuffle @F]}” 结构创建了一个指向“shuffle @F”内容的数组引用,并且通过“@ {…}”将其解引用成其内容。这是一个执行插入到引用中代码的常用技巧。这方法于我们需要把@F顺序打乱,并以空格分割输出是必须的。

另一种方法可以实现同样的功能,是通过join把@F的各个元素和空格连接起来,但是代码会更长一点:

25.找到每一行的最小的元素

本行代码使用了“List::Util”模块的“min”函数,形式和前面几例都相似。每行通过“-a”选项自动分割,并用“min”函数求出最小的元素并打印出来。

26.找到所有行中最小的元素。

这行代码是上一例子以及例23的整合。“@M=(@M,@F)”结构相当于“push @M,@F”,把@F的内容附加到数组@M中。

本代码需要把所有数据都存进内存。如果你用它去处理10T大的文件,他肯定会挂掉。因此,因此更好一点的方法是只把最小等数存进内存,最后输出:

代码会找出每一个行最小的一个并存进 $min,接着检查他是不是比当前最小数$rmin还小。最后打印当前最小数,保证了在所有的输人中他是最小的一个值。

27.找出一行中最大的数

和例25完全相同,只不过函数用最大函数“max”。

28.找出所有行中最小的一个

和例子26一样,也有另一种形式:

29.对所有的域的值用其绝对值代替

本行代码,先通过“-a”命令行参数自动分割行的各个域,接着调用绝对值函数“abs”,通过map对其进行整体操作。最后利用匿名数组方式将各个域连接。@{ … }解释见例24.

30.找到每行域个数

本行代码强制@F到标量上下文,这在perl中就是@F的元素个数。因此输出结果就是每行的域个数。

31.输出每行,并在每行前面显示域数量。

本例和例30基本上一样,除了把每行的内容“$_”也打印出来了。(提示,“-n”选项会导致进行输入循环,并把每行的内容存储在$_变量)。

32.计算所有行的域总数

在这我们仅仅通过把每行的域的数量累加,并用变量“$t”保存,最后输出。结果就是所有域的总数。

33.打印出匹配模式的域总数。

这行代码使用map函数对@F数组的各个元素进行操作。本例中是检查其是否匹配模式/regex/,如果是的话,给变量$t加1。在最后END模块中打印出$t的值,即为匹配的域的总数。

还有一种比较好的方法是:

每一个@F的元素用/regex/模式检验。如果匹配/regex/会返回值1(表示真),我们用+=操作符累加并赋值给$t.用这种方式使得匹配的数量会被保存在$t。 最好的方式是利用标量上下文的grep:

在标量上下文grep会返回匹配的数量,数量也用$t进行累加。

34.打印所有匹配模式的行总数

如果当前行输出匹配正则模式,/regex/为true。/regex/ && $t++相当于if ($_ =~ /regex/) { $t++ },如果模式匹配,$t值就会加1.最后END模块输出$t值,就是匹配模式的行的数量。

35.打印PI的值到n位小数点后。

bignum库的bpi函数可以计算输出PI常量到所需足够精确地值。本例打印PI到小数点后20位。 bignum库也可以直接输出PI常量,这个数值保留39位的精确度:

36.打印E的值到n为小数点后。

Bignum库也可以利用bexp函数,来计算和输出e及其n方幂的精确值,他的两个参数为幂值n以及精确位。本里打印了e的值(1一次方)到20位小数点后。

37.打印e的平方到31位精确值:

和PI一样,binum也支持输出常量e,值也保持39位的精确度。

38.打印Unix 时间值(自从1970年一月一日, 00:00:00 UTC后累计秒的值)

内建的函数“time”返回公元以来的秒数。

39.打印GMT(格林威治时间)和本机时间。

“gmtime”函数是Perl内建的函数。如果使用标量上下文,他会打印格林威治时区(0时区)标准时间。

“localtime”内建函数,表现和“gmtime”大致一样,不过他显示的计算机本地的时间。 “gmtime”和“localtime”均返回9元素的列表(tm结构体),包含下述元素:

根据实际的需要信息,你可以对此列表做切片,或者打印单个元素。例如,为了打印H:M:S,切割出localtime的2,1和0元素:

40. 打印昨天的日期

上例说了localtime返回一个9元素的各种时间项。其中第4项是当前日期数。如果我们对其减去1就得到昨天。“mktime”函数利用修改过的9项目列表构建一个Unix时间戳。最后“scalar localtime”打印了这个新的日期,就是昨天。

因为使用到了mktime函数,POSIX包是必须的,他应该是用于规范化负值。

41.打印14个月9天7秒前的日期

本行代码修改了@now列表的第1个,第5个以及第8个元素。第1个是秒数,第5个是月,第8个是日(见上面时间9元素表). 接着利用mktime生成把它转化成了Unix时间戳,再利用标量上下文的localtime打印日期:14个月9天7秒钟前。

42.计算阶乘。

本行代码使用Math::BigInt模块的bfac()函数,这个模块内置与Perl核心(不必另行安装)。

Math::BigInt->new(5)结构创建一个新的Math::BigInt对象,构造参数为5。接着用bfac()方法调用新创建的对象计算5的阶乘。如果计算其他数的阶乘,只需要在创建对象时候,更换构造参数的值为所希望的值即可。

另一种计算阶乘的方法是直接累乘从1到n的值:

我们给$f初始化赋值为1.接着做一个从1到5的循环,对$f进行各个值的累乘。结果是12345,这就是5的阶乘。

43.计算最大公约数。

Math::BigInt还内带有其他好多个非常有用的数学函数。这其中之一是bgcd,用来计算一列数字的最大公约数。

当然,你也可以使用欧几里德算法(即辗转相除法)。给予两个数字$n和$m,这行代码找出两者的gcd。结果保存在$m中。

44.计算最小公倍数。

另外一个函数是lcm,计算最小公倍数,一下行代码计算(35,20,8)的最小公倍数。

如果你了解一些数字定理,你应该能知道gcd和lcm之间是有联系的。给定两个数字$n和$m,他们的lcm等于$n*$m/gcd($n,$m),因此,也就是另一种计算最小公倍数的行代码:

45.生成5到15之间(包括15)的10个随机数

你能通过改变$n,$min,$max调整此行代码。变量$n表示要生成多少个随机数,[$min,$max)表示生成数的访问。

变量$,设为空格,用来格式化输出的域间隔,默认没有设置。所以如果我们不设置它为空格,打印的数字将会连在一起。

46.找出列表的所有排列方式并打印出来。

这行代码使用面向对象的接口Algorithm::Permute模块来找出排列(所有重新组合元素的方法)。

Algorithm::Permute的构造方法接受一个数组引用参数来排列。本例中元素为1,2,3,4,5。

对象方法的方法next返回下一个排列。循环调用这个方法可以获得所有可能的排列。

我们可以注意到很快就能输出大量的列表。对一个n个元素的列表来说有n!种排列。

另一种方法是使用permute子函数。

47.生成幂集。

本例,我使用了CPAN的List::PowerSet模块。我输出powerset函数,本函数以一列表做为参数,返回一个引用,该应用为指向子集列表的引用的列表。 在for循环调用了powerset函数,传递给它@l的列表元素。接着,解引用返回powerset的值,这个值是一个指向子集列表的引用。最后,解引用每一个子集@$_并打印它。

对一个n元素的集合,他的幂集有2n个子集。

48.转换IP地址为无符号整数。

本行代码转换127.0.0.1的IP地址为无符号整数(他恰巧是2130706433)。

首先对IP地址做了个(\d+)全局匹配。做一个for循环做全局匹配的迭代,使所有数字得到匹配,匹配了IP地址的四个部分。 接着把匹配的数字加起来保存在$u中,其中第一部分位移83=24位,第二部分位移28=16位,第三部分8位,最后一部分直接加给$u。 但是本行代码没有对IP地址做任何的格式检验。你也可以使用一个更复杂一点正则表达式来检测,比如/^(\d+).(\d+).(\d+).(\d+)$/g。 经过和朋友一起讨论,我们想出另外一些方法:

这个行代码巧妙地利用127.0.0.1可以被容易的转化为十六进程 7f000001的事实,再利用Perl的hex函数转化为十进制。

另一种方法是利用unpack:

这个行代码大概是能达到最短的一个。它利用vstring语法(版本字符)表达IP地址。Vstring格式是指用特定顺序值组成的字符串。接着,利用网络字节顺序(Big-Endian顺序)新的格式化字符串被解包为一个数字,并把它打印出来。

如果有一个IP的字符串(不是一个vstring),你首先就要用inet_aton函数转化这个字符串为字节的形式:

这儿inet_aton函数转化字符串“127.0.0.1 ”为字节的形式(这恰恰和纯vstring 127.0.0.1是一样的),接着unpack它。

49.转化一个无符号整数为IP地址。

本例中整数2130706433首先被pack为一个Big-Endian数字,并且利用inet_ntoa函数转化这个数字为IP地址。

另外一种方法是用位移和逐字节打印的方式:

随便说一下,join “.”可以被特殊变量$,代替,它专门用来做为打印语句的默认分割符: