如何获取文件中的最大数字?

时间:2015-06-02 09:29:47

标签: performance bash sorting

我想获取文件中的最大数字,其中数字是可以在文件的任何位置出现的整数。

我考虑过做以下事情:

grep -o '[-0-9]*' myfile | sort -rn | head -1

这使用grep从文件中获取所有整数,每行输出一个整数。然后,sort对它们进行排序,head打印第一个。

但后来认为sort -r可能会导致一些开销,所以我选择了:

grep -o '[-0-9]*' myfile | sort -n | tail -1

要查看最快的内容,我创建了一个包含一些随机数据的大文件,如下所示:

$ 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

这样该文件包含150K行。

现在,我比较GNU bash version 4.2sys的效果对sort -rn的影响较小:

$ 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 -1sort -rn | head -1
  • 是否有最快的方法来获取给定文件中的最大整数?

测试解决方案

所以我运行了所有命令并比较了获取它们的时间。为了使事情更可靠,我创建了一个更大的文件,比我在问题中提到的文件大10倍:

$ 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's solution是最快的:

$ 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 ' ' '\n' < 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

4 个答案:

答案 0 :(得分:3)

在awk中你可以说:

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 ++编写的程序。

在上面的代码中,使用最少的函数和命令可以更快地执行。

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.           

基准

矿:

time awk '{for(i=1;i<=NF;i++)if(int($i)){a[$i]=$i}}END{x=asort(a);print a[x]}' file

real    0m0.434s
user    0m0.357s
sys     0m0.008s

<强> hek2mgl's

awk '{m=(m<$0 && int($0))?$0:m}END{print m}' RS='[[:space:]*]' file

real    0m1.256s
user    0m1.134s
sys     0m0.019s

对于那些想知道为什么它更快的人来说,由于使用了默认的FS和RS,awk被优化用于

更改

awk '{m=(m<$0 && int($0))?$0:m}END{print m}' RS='[[:space:]*]'

awk '{for(i=1;i<=NF;i++)m=(m<$i && int($i))?$i:m}END{print m}'

提供时间

real    0m0.574s
user    0m0.497s
sys     0m0.011s

这仍然比我的命令慢一点。

我认为仍然存在的微小差异是由于asort()仅处理了大约6个数字,因为它们只在数组中保存一次。

相比之下,另一个命令正在对文件中的每个数字进行比较,这将在计算上更加昂贵。

如果文件中的所有数字都是唯一的,我认为它们的速度大致相同。

<强> Tom Fenech's

 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's

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

time perl -MList::Util=max -0777 -nE 'say max /-?\d+/g' file

real    0m0.790s
user    0m0.686s
sys     0m0.034s

关于perl -MList::Util=max -0777 -nE 'say max /-?\d+/g'的好处是,如果0在文件中作为最大数字出现,那么这里唯一的答案是有用的,如果所有数字都是负数,也是有效的。

注释

所有时间都代表3次测试的平均值

答案 1 :(得分:3)

我对awk的速度感到惊讶。 perl通常很快,但是:

$ 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,将文件作为单个字符串啜饮,找到(可能是负数)整数,然后取最大值:

$ time perl -MList::Util=max -0777 -nE 'say max /-?\d+/g' rand
32767

real    0m0.565s
user    0m0.539s
sys 0m0.025s

需要多一点&#34; sys&#34;时间,但不太实时。

使用仅包含负数的文件:

$ cat file
hello -42 world
$ perl -MList::Util=max -0777 -nE 'say max /-?\d+/g' file
-42

答案 2 :(得分:2)

我确定使用汇编程序优化的C实现将是最快的。此外,我可以想到一个程序将文件分成多个块并将每个块映射到单个处理器核心,然后只获取nproc重新生成数字的最大值。

只需使用现有的命令行工具,您是否尝试过awk

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%的时间内完成工作:

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

给我:

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

答案 3 :(得分:2)

我怀疑这会是最快的:

$ tr ' ' '\n' < file | sort -rn | head -1
42342234

第三次运行:

$ time tr ' ' '\n' < file | sort -rn | head -1
42342234
real    0m0.078s
user    0m0.000s
sys     0m0.076s

顺便说一句,即使它正在创建示例输入文件,也不要编写SHELL LOOPS来操作文本:

$ 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循环相比:

$ 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

如果我们想要更强大地处理包含不是整数的字符串中的数字的输入文件,我们需要这样的东西:

$ 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解决方案实际上比我在此问题开头时发布的原始文档更快:

$ time tr ' ' '\n' < myfileB | sort -rn | head -1
42342234
real    0m4.836s
user    0m4.445s
sys     0m0.277s