Files
devops/perl/perlonelinecn-master/03数值计算.md
2025-09-17 16:08:16 +08:00

302 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 数值计算
## 21.检查一个数字是否是质数
perl -lne '(1x$_) !~ /^1?$|^(11+?)\1+$/ && print "$_ is prime"'
这行代码巧妙使用一个正则表达式判断给予的数字是不是质数。不要想得太复杂了,我引用这个例子主要为了说明其他的技巧性。
首先这个数字通过“1x$_”被转变为他一元表达式。例如,5被转化了“1x5”也就是“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.打印一行中所有的域的和。
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.打印所有行所有域的值
perl -MList::Util=sum -alne 'push @S,@F; END { print sum @S }'
这行代码通过push操作把每行分割的@F数组保存到@S数组中去当所有行都输入后perl退出前END{}块被执行调用sum函数对@S的所有成员求和。这个和就是所有行所有域的和。
这个方案不是最好选择。它生成了一个额外的数组@S,最好的方式是是下面的:
perl -MList::Util=sum -alne '$s += sum @F; END { print $s }'
## 24.以随机顺序显示每行的各个域。
perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"'
本行和例22基本上相同只不过本例使用shuffle函数将行域顺序随机打乱并显示出来。
“@{[shuffle @F]}” 结构创建了一个指向“shuffle @F”内容的数组引用,并且通过“@ {…}”将其解引用成其内容。这是一个执行插入到引用中代码的常用技巧。这方法于我们需要把@F顺序打乱,并以空格分割输出是必须的。
另一种方法可以实现同样的功能是通过join把@F的各个元素和空格连接起来,但是代码会更长一点:
perl -MList::Util=shuffle -alne 'print join " ", shuffle @F'
## 25.找到每一行的最小的元素
perl -MList::Util=min -alne 'print min @F'
本行代码使用了“List::Util”模块的“min”函数形式和前面几例都相似。每行通过“-a”选项自动分割并用“min”函数求出最小的元素并打印出来。
## 26.找到所有行中最小的元素。
perl -MList::Util=min -alne '@M = (@M, @F); END { print min @M }'
这行代码是上一例子以及例23的整合。“@M=@M,@F”结构相当于“push @M,@F”,把@F的内容附加到数组@M中
本代码需要把所有数据都存进内存。如果你用它去处理10T大的文件他肯定会挂掉。因此因此更好一点的方法是只把最小等数存进内存最后输出
perl -MList::Util=min -alne '$min = min @F; $rmin = $min unless defined $rmin && $min > $rmin; END { print $rmin }'
代码会找出每一个行最小的一个并存进 $min,接着检查他是不是比当前最小数$rmin还小。最后打印当前最小数保证了在所有的输人中他是最小的一个值。
## 27.找出一行中最大的数
perl -MList::Util=max -alne 'print max @F'
和例25完全相同只不过函数用最大函数“max”。
## 28.找出所有行中最小的一个
perl -MList::Util=max -alne '@M = (@M, @F); END { print max @M }'
和例子26一样也有另一种形式
perl -MList::Util=max -alne '$max = max @F; $rmax = $max unless defined $rmax && $max < $rmax; END { print $rmax }'
## 29.对所有的域的值用其绝对值代替
perl -alne 'print "@{[map { abs } @F]}"'
本行代码,先通过“-a”命令行参数自动分割行的各个域接着调用绝对值函数“abs”通过map对其进行整体操作。最后利用匿名数组方式将各个域连接。@{ ... }解释见例24.
## 30.找到每行域个数
perl -alne 'print scalar @F'
本行代码强制@F到标量上下文这在perl中就是@F的元素个数。因此输出结果就是每行的域个数。
## 31.输出每行,并在每行前面显示域数量。
perl -alne 'print scalar @F, " $_"'
本例和例30基本上一样除了把每行的内容“$_”也打印出来了。提示“-n”选项会导致进行输入循环并把每行的内容存储在$_变量
## 32.计算所有行的域总数
perl -alne '$t += @F; END { print $t}'
在这我们仅仅通过把每行的域的数量累加,并用变量“$t”保存最后输出。结果就是所有域的总数。
## 33.打印出匹配模式的域总数。
perl -alne 'map { /regex/ && $t++ } @F; END { print $t }'
这行代码使用map函数对@F数组的各个元素进行操作。本例中是检查其是否匹配模式/regex/,如果是的话,给变量$t加1。在最后END模块中打印出$t的值即为匹配的域的总数。
还有一种比较好的方法是:
perl -alne '$t += /regex/ for @F; END { print $t }'
每一个@F的元素用/regex/模式检验。如果匹配/regex/会返回值1表示真我们用+=操作符累加并赋值给$t.用这种方式使得匹配的数量会被保存在$t。
最好的方式是利用标量上下文的grep
perl -alne '$t += grep /regex/, @F; END { print $t }'
在标量上下文grep会返回匹配的数量数量也用$t进行累加。
## 34.打印所有匹配模式的行总数
perl -lne '/regex/ && $t++; END { print $t }'
如果当前行输出匹配正则模式,/regex/为true。/regex/ && $t++相当于if ($_ =~ /regex/) { $t++ },如果模式匹配,$t值就会加1.最后END模块输出$t值就是匹配模式的行的数量。
## 35.打印PI的值到n位小数点后。
perl -Mbignum=bpi -le 'print bpi(21)'
bignum库的bpi函数可以计算输出PI常量到所需足够精确地值。本例打印PI到小数点后20位。
bignum库也可以直接输出PI常量这个数值保留39位的精确度
perl -Mbignum=PI -le 'print PI'
## 36.打印E的值到n为小数点后。
perl -Mbignum=bexp -le 'print bexp(1,21)'
Bignum库也可以利用bexp函数来计算和输出e及其n方幂的精确值他的两个参数为幂值n以及精确位。本里打印了e的值1一次方到20位小数点后。
## 37.打印e的平方到31位精确值
perl -Mbignum=bexp -le 'print bexp(2,31)'
和PI一样binum也支持输出常量e值也保持39位的精确度。
perl -Mbignum=e -le 'print e'
## 38.打印Unix 时间值自从1970年一月一日, 00:00:00 UTC后累计秒的值
perl -le 'print time'
内建的函数“time”返回公元以来的秒数。
## 39.打印GMT格林威治时间和本机时间。
perl -le 'print scalar gmtime'
“gmtime”函数是Perl内建的函数。如果使用标量上下文他会打印格林威治时区0时区标准时间。
perl -le 'print scalar localtime'
“localtime”内建函数表现和“gmtime”大致一样不过他显示的计算机本地的时间。
“gmtime”和“localtime”均返回9元素的列表tm结构体包含下述元素
($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的21和0元素
perl -le 'print join ":", (localtime)[2,1,0]'
## 40. 打印昨天的日期
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秒前的日期
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.计算阶乘。
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的值
perl -le '$f = 1; $f *= $_ for 1..5; print $f'
我们给$f初始化赋值为1.接着做一个从1到5的循环对$f进行各个值的累乘。结果是1*2*3*4*5,这就是5的阶乘。
## 43.计算最大公约数。
perl -MMath::BigInt=bgcd -le 'print bgcd(@list_of_numbers)'
Math::BigInt还内带有其他好多个非常有用的数学函数。这其中之一是bgcd用来计算一列数字的最大公约数。
perl -MMath::BigInt=bgcd -le 'print bgcd(20,60,30)' 当然,你也可以使用欧几里德算法(即辗转相除法)。给予两个数字$n和$m,这行代码找出两者的gcd。结果保存在$m中。 perl -le '$n = 20; $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $m'
## 44.计算最小公倍数。
另外一个函数是lcm计算最小公倍数一下行代码计算35208的最小公倍数。
perl -MMath::BigInt=blcm -le 'print blcm(35,20,8)'
如果你了解一些数字定理你应该能知道gcd和lcm之间是有联系的。给定两个数字$n和$m,他们的lcm等于$n*$m/gcd($n,$m),因此,也就是另一种计算最小公倍数的行代码:
perl -le '$a = $n = 20; $b = $m = 35; ($m,$n) = ($n,$m%$n) while $n; print $a*$b/$m'
## 45.生成5到15之间包括15的10个随机数
perl -le '$n=10; $min=5; $max=15; $, = " "; print map { int(rand($max-$min))+$min } 1..$n'
你能通过改变$n,$min,$max调整此行代码。变量$n表示要生成多少个随机数[$min,$max)表示生成数的访问。
变量$,设为空格,用来格式化输出的域间隔,默认没有设置。所以如果我们不设置它为空格,打印的数字将会连在一起。
## 46.找出列表的所有排列方式并打印出来。
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子函数。
perl -MAlgorithm::Permute -le '@l = (1,2,3,4,5);Algorithm::Permute::permute { print "@l" } @l'
## 47.生成幂集。
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地址为无符号整数。
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中其中第一部分位移8*3=24位第二部分位移2*8=16位第三部分8位最后一部分直接加给$u。
但是本行代码没有对IP地址做任何的格式检验你也可以使用一个更复杂一点正则表达式来检测比如/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/g
经过和朋友一起讨论我们想出另外一些方法
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
perl -le 'print unpack("N", 127.0.0.1)'
这个行代码大概是能达到最短的一个它利用vstring语法版本字符表达IP地址Vstring格式是指用特定顺序值组成的字符串接着利用网络字节顺序Big-Endian顺序新的格式化字符串被解包为一个数字并把它打印出来
如果有一个IP的字符串不是一个vstring你首先就要用inet_aton函数转化这个字符串为字节的形式
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地址。
perl -MSocket -le 'print inet_ntoa(pack("N", 2130706433))'
本例中整数2130706433首先被pack为一个Big-Endian数字并且利用inet_ntoa函数转化这个数字为IP地址
另外一种方法是用位移和逐字节打印的方式
perl -le '$ip = 2130706433; print join ".", map { (($ip>>8*($_))&0xFF) } reverse 0..3'
随便说一下join “.”可以被特殊变量$,代替,它专门用来做为打印语句的默认分割符:
perl -le '$ip = 2130706433; $, = "."; print map { (($ip>>8*($_))&0xFF) } reverse 0..3'