有没有更快的方法来找到最小值和最大值?

时间:2011-05-04 12:45:57

标签: wolfram-mathematica mathematica-7

我经常写:{Min@#, Max@#} &

然而这似乎效率低下,因为表达式必须扫描两次,一次找到最小值,一次找到最大值。有更快的方法吗?表达式通常是张量或数组。

4 个答案:

答案 0 :(得分:5)

这有点打败它。

minMax = Compile[{{list, _Integer, 1}},
   Module[{currentMin, currentMax},
    currentMin = currentMax = First[list];
    Do[
     Which[
      x < currentMin, currentMin = x,
      x > currentMax, currentMax = x],
     {x, list}];
    {currentMin, currentMax}],
   CompilationTarget -> "C",
   RuntimeOptions -> "Speed"];
v = RandomInteger[{0, 1000000000}, {10000000}];
minMax[v] // Timing

我认为它比Leonid的版本快一点,因为DoFor快一点,即使在编译代码中也是如此。

但最终,这是使用高级函数式编程语言时性能影响的一个例子。

对Leonid的回应:

我不认为该算法可以解释所有的时差。 很多,我认为,无论如何都会应用这两种测试。但是,DoFor之间的差异是可衡量的。

cf1 = Compile[{{list, _Real, 1}},
   Module[{sum},
    sum = 0.0;
    Do[sum = sum + list[[i]]^2,
     {i, Length[list]}];
    sum]];
cf2 = Compile[{{list, _Real, 1}},
   Module[{sum, i},
    sum = 0.0;
    For[i = 1, i <= Length[list],
     i = i + 1, sum = sum + list[[i]]^2];
    sum]];
v = RandomReal[{0, 1}, {10000000}];
First /@{Timing[cf1[v]], Timing[cf2[v]]}

{0.685562, 0.898232}

答案 1 :(得分:3)

在Mathematica编程实践中,我认为这是最快的。我认为在mma中尝试使其更快的唯一方法是将Compile与C编译目标一起使用,如下所示:

getMinMax = 
 Compile[{{lst, _Real, 1}},
   Module[{i = 1, min = 0., max = 0.},
       For[i = 1, i <= Length[lst], i++,
        If[min > lst[[i]], min = lst[[i]]];
        If[max < lst[[i]], max = lst[[i]]];];
        {min, max}], CompilationTarget -> "C", RuntimeOptions -> "Speed"]

然而,即使这似乎比你的代码慢一点:

In[61]:= tst = RandomReal[{-10^7,10^7},10^6];

In[62]:= Do[getMinMax[tst],{100}]//Timing
Out[62]= {0.547,Null}

In[63]:= Do[{Min@#,Max@#}&[tst],{100}]//Timing
Out[63]= {0.484,Null}

你可能完全用C编写函数,然后编译并加载为dll - 你可以通过这种方式消除一些开销,但我怀疑你会赢得超过几个百分点 - 不值得IMO的努力。

修改

有趣的是,您可以使用手动公共子表达式消除显着提高编译解决方案的速度(lst[[i]]此处):

getMinMax = 
 Compile[{{lst, _Real, 1}},
   Module[{i = 1, min = 0., max = 0., temp},
     While[i <= Length[lst],
       temp = lst[[i++]];
       If[min > temp, min = temp];
       If[max < temp, max = temp];];
       {min, max}], CompilationTarget -> "C", RuntimeOptions -> "Speed"]

{Min@#,Max@#}快一点。

答案 2 :(得分:3)

对于数组,您可以执行最简单的功能并使用

Fold[{Min[##],Max[##]}&, First@#, Rest@#]& @ data

不幸的是,它不是速度恶魔。即使对于短列表,5个元素,Leonid's answersMark's answer都至少快7倍,在第7版中未编译。对于长列表,25000个元素,这种情况变得更糟,Mark的速度提高了19.6倍,即使在这个长度上,这个解决方案也只需要0.05秒就可以运行。

但是,我不会指望{Min[#], Max[#]}&作为选项。未编译它比Mark的短名单快1.7倍,长列表快15倍(分别比Fold解决方案快8倍和近300倍)。

不幸的是,我无法为{Min[#], Max[#]}&,Leonid或Mark的答案的编译版本获得好的数字,而是我收到了许多难以理解的错误消息。实际上,{Min[#], Max[#]}&执行时间增加了。尽管如此,Fold解决方案得到了显着改善,并且花费了Leonid的答案“未编译时间”的两倍。

编辑:对于好奇,这里是未编译函数的一些时序测量 -

timing measurements on a log-log plot

每个函数用于100个水平轴上指定长度的列表,平均时间(秒)是垂直轴。按照时间的升序,曲线是{Min[#], Max[#]}&,马克的答案,列昂尼德的第二个答案,列昂尼德的第一个答案,以及上面的Fold方法。

答案 3 :(得分:2)

对于所有正在做时间的人,我想警告你,执行的顺序非常重要。例如,看看以下两个略有不同的时序测试:

(1)

res =
 Table[
  a = RandomReal[{0, 100}, 10^8];
  {
   Min[a] // AbsoluteTiming // First, Max[a] // AbsoluteTiming // First,
   Max[a] // AbsoluteTiming // First, Min[a] // AbsoluteTiming // First
   }
  , {100}
  ]

enter image description here
这里的奇怪人物是最后一个Min

(2)

res =
 Table[
  a = RandomReal[{0, 100}, 10^8];
  {
   Max[a] // AbsoluteTiming // First, Min[a] // AbsoluteTiming // First,
   Min[a] // AbsoluteTiming // First, Max[a] // AbsoluteTiming // First
   }
  , {100}
  ]

enter image description here

此处,找到第一个Max的最高时间,第二个max的第二个最高时间,两个Min的时间大致相同且最低。实际上,我希望MaxMin大约需要同一时间,但事实并非如此。前者似乎比后者花费多50%的时间。杆位也似乎有50%的差点。

现在与Mark和Leonid给出的算法进行比较:

res =
 Table[
  a = RandomReal[{0, 100}, 10^8];
  {
   {Max[a], Min[a]} // AbsoluteTiming // First,
   {Min@#, Max@#} &@a // AbsoluteTiming // First,
   getMinMax[a] // AbsoluteTiming // First,
   minMax[a] // AbsoluteTiming // First,
   {Min[a], Max[a]} // AbsoluteTiming // First
   }
  , {100}
  ]

enter image description here

这里我们发现{Max [a],Min [a]}(包括杆位障碍)的.3 s,.1级是Mark的方法;其他人都差不多。