How to get the biggest number in a file?
我想获取文件中的最大数字,其中数字是可以在文件任何位置出现的整数。
我考虑过要进行以下操作:
1 | grep -o '[-0-9]*' myfile | sort -rn | head -1 |
这使用
但是后来认为
1 | grep -o '[-0-9]*' myfile | sort -n | tail -1 |
为了查看最快的速度,我创建了一个包含一些随机数据的大文件,如下所示:
1 2 3 4 5 | $ cat a hello 123 how are you i am fine 42342234 and blab bla bla and 3624 is another number but this is not enough for -23 234245 $ for i in {1..50000}; do cat a >> myfile ; done |
文件包含15万行。
现在,我比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ time grep -o '[-0-9]*' myfile | sort -n | tail -1 42342234 real 0m1.823s user 0m1.865s sys 0m0.045s $ cp myfile myfile2 #to prevent using cached info $ time grep -o '[-0-9]*' myfile2 | sort -rn | head -1 42342234 real 0m1.864s user 0m1.926s sys 0m0.027s |
所以我在这里有两个问题:
-
最好是
sort -r | tail -1 或sort -rn | head -1 ? - 有没有最快的方法来获取给定文件中的最大整数?
测试解决方案
因此,我运行了所有命令,并比较了它们获取值的时间。为了使事情更可靠,我创建了一个更大的文件,该文件的大小是我在问题中提到的文件的10倍:
1 2 3 4 5 6 7 | $ cat a hello 123 how are you i am fine 42342234 and blab bla bla and 3624 is another number but this is not enough for -23 234245 $ time awk -v s="$(cat a)" 'BEGIN{for (i=1;i<=500000;i++) print s}' > myfile $ wc myfile 1500000 13000000 62000000 myfile |
基准测试,从中可以看到hek2mgl的解决方案是最快的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | $ time awk 'NR==1 || max < 0+$0 {max=0+$0} END {print max}' RS='[[:space:]]+' myfile 42342234 real 0m3.979s user 0m3.970s sys 0m0.007s $ time awk '{for(i=1;i<=NF;i++)if(int($i)){a[$i]=$i}}END{x=asort(a);print a[x]}' myfile 42342234 real 0m2.203s user 0m2.196s sys 0m0.006s $ time awk '{for(i=1;i<=NF;i++){m=(m<$i)?$i:m}}END{print m}' RS='$' FPAT='-{0,1}[0-9]+' myfile 42342234 real 0m0.926s user 0m0.848s sys 0m0.077s $ time tr ' ' '\ ' < myfile | sort -rn | head -1 42342234 real 0m11.089s user 0m11.049s sys 0m0.086s $ time perl -MList::Util=max -lane '$m = max $m, map {0+$_} @F} END {print $max' myfile real 0m6.166s user 0m6.146s sys 0m0.011s |
我对awk在这里的速度感到惊讶。 perl通常非常快速,但是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ for ((i=0; i<1000000; i++)); do echo $RANDOM; done > rand $ time awk 'NR==1 || max < 0+$0 {max=0+$0} END {print max}' RS='[[:space:]]+' rand 32767 real 0m0.890s user 0m0.887s sys 0m0.003s $ time perl -MList::Util=max -lane '$m = max $m, map {0+$_} @F} END {print $max' rand 32767 real 0m1.110s user 0m1.107s sys 0m0.002s |
我想我找到了一个赢家:使用perl,将文件作为单个字符串处理,找到(可能是负数)整数,并采用max:
1 2 3 4 5 6 | $ time perl -MList::Util=max -0777 -nE 'say max /-?\\d+/g' rand 32767 real 0m0.565s user 0m0.539s sys 0m0.025s |
花费更多的" sys"时间,但是减少了实时时间。
也可以处理仅带有负数的文件:
1 2 3 4 | $ cat file hello -42 world $ perl -MList::Util=max -0777 -nE 'say max /-?\\d+/g' file -42 |
在awk中,您可以说:
1 | awk '{for(i=1;i<=NF;i++)if(int($i)){a[$i]=$i}}END{x=asort(a);print a[x]}' file |
解释
根据我的经验,awk是用于大多数任务的最快的文本处理语言,而我所见的唯一速度可比的东西(在Linux系统上)是用C / C编写的程序。
在上面的代码中,使用最少的功能和命令将加快执行速度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | for(i=1;i<=NF;i++) - Loops through fields on the line. Using the default FS/RS and looping this way is usually faster than using custom ones as awk is optimised to use the default if(int($i)) - Checks if the field is not equal to zero and as strings are set to zero by int, does not execute the next block if the field is a string. I believe this is the quickest way to perform this check {a[$i]=$i} - Sets an array variable with the number as key and value. This means there will only be as many array variables as there are numbers in the file and will hopefully be quicker than a comparison of every number END{x=asort(a) - At the end of the file, use asort on the array and store the s size of the array in x. print a[x] - Print the last element in the array. |
基准
矿山:
1 | time awk '{for(i=1;i<=NF;i++)if(int($i)){a[$i]=$i}}END{x=asort(a);print a[x]}' file |
接
1 2 3 | real 0m0.434s user 0m0.357s sys 0m0.008s |
hek2mgl's:
1 | awk '{m=(m<$0 && int($0))?$0:m}END{print m}' RS='[[:space:]*]' file |
接
1 2 3 | real 0m1.256s user 0m1.134s sys 0m0.019s |
对于那些想知道为什么它更快的原因是使用默认的FS和RS,而awk已针对使用
进行了优化
更改
1 | awk '{m=(m<$0 && int($0))?$0:m}END{print m}' RS='[[:space:]*]' |
到
1 | awk '{for(i=1;i<=NF;i++)m=(m<$i && int($i))?$i:m}END{print m}' |
提供时间
1 2 3 | real 0m0.574s user 0m0.497s sys 0m0.011s |
哪个仍然比我的命令慢一些。
我相信仍然存在的细微差异是由于
相比之下,另一个命令正在对文件中的每个数字执行比较,这将在计算上更加昂贵。
我认为,如果文件中的所有数字都是唯一的,它们的速度将大致相同。
汤姆·费内奇(Tom Fenech):
1 2 3 4 5 | time awk -v RS="[^-0-9]+" '$0>max{max=$0}END{print max}' myfile real 0m0.716s user 0m0.612s sys 0m0.013s |
这种方法的缺点是,如果所有数字都小于零,则max将为空白。
格伦·杰克曼(Glenn Jackman):
1 2 3 4 5 | time awk 'NR==1 || max < 0+$0 {max=0+$0} END {print max}' RS='[[:space:]]+' file real 0m1.492s user 0m1.258s sys 0m0.022s |
和
1 2 3 4 5 | time perl -MList::Util=max -0777 -nE 'say max /-?\\d+/g' file real 0m0.790s user 0m0.686s sys 0m0.034s |
关于
笔记
所有时间均代表3次测试的平均值
我怀疑这将是最快的:
1 2 3 | $ tr ' ' '\ ' < file | sort -rn | head -1 42342234 |
第三次运行:
1 2 3 4 5 6 | $ time tr ' ' '\ ' < file | sort -rn | head -1 42342234 real 0m0.078s user 0m0.000s sys 0m0.076s |
btw即使编写示例输入文件,也不要写壳框来操纵文本:
1 2 3 4 5 6 7 8 | $ time awk -v s="$(cat a)" 'BEGIN{for (i=1;i<=50000;i++) print s}' > myfile real 0m0.109s user 0m0.031s sys 0m0.061s $ wc -l myfile 150000 myfile |
与问题中建议的shell循环相比:
1 2 3 4 5 6 7 8 | $ time for i in {1..50000}; do cat a >> myfile2 ; done real 26m38.771s user 1m44.765s sys 17m9.837s $ wc -l myfile2 150000 myfile2 |
如果我们想要更强大的功能来处理包含非整数字符串中的数字的输入文件,则需要这样的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | $ cat b hello 123 how are you i am fine 42342234 and blab bla bla and 3624 is another number but this is not enough for -23 234245 73 starts a line avoid these: 3.14 or 4-5 or $15 or 2:30 or 05/12/2015 $ grep -o -E '(^| )[-]?[0-9]+( |$)' b | sort -rn 42342234 3624 123 73 -23 $ time awk -v s="$(cat b)" 'BEGIN{for (i=1;i<=50000;i++) print s}' > myfileB real 0m0.109s user 0m0.000s sys 0m0.076s $ wc -l myfileB 250000 myfileB $ time grep -o -E '(^| )-?[0-9]+( |$)' myfileB | sort -rn | head -1 | tr -d ' ' 42342234 real 0m2.480s user 0m2.509s sys 0m0.108s |
请注意,输入文件的行数比原始文件多,使用此输入,上述可靠的grep解决方案实际上比我在此问题开始时发布的原始文件要快:
1 2 3 4 5 6 | $ time tr ' ' '\ ' < myfileB | sort -rn | head -1 42342234 real 0m4.836s user 0m4.445s sys 0m0.277s |
我敢肯定,使用汇编程序优化的C实现将是最快的。我还可以想到一个程序,它将文件分成多个块,然后将每个块映射到单个处理器内核,然后获取nproc剩余数量的最大值。
仅使用现有的命令行工具,您是否尝试过
1 | time awk '{for(i=1;i<=NF;i++){m=(m<$i)?$i:m}}END{print m}' RS='$' FPAT='-{0,1}[0-9]+' myfile |
与公认的答案中的perl命令相比,它可以在大约50%的时间内完成这项工作:
1 2 3 4 | time perl -MList::Util=max -0777 -nE 'say max /-?\\d+/g' myfile cp myfile myfile2 time awk '{for(i=1;i<=NF;i++){m=(m<$i)?$i:m}}END{print m}' RS='$' FPAT='-{0,1}[0-9]+' myfile2 |
给我:
1 2 3 4 5 6 7 8 9 10 | 42342234 real 0m0.360s user 0m0.340s sys 0m0.020s 42342234 real 0m0.193s <-- Good job awk! You are the winner. user 0m0.185s sys 0m0.008s |