今天的主题是计算最大值。确切地说,我们需要找出计算最大值最有效的方式。

写这个内容的直接原因是最近看一位朋友的Excel文件。这个文件有点慢,仔细看发现瓶颈在一个计算最大值的列上。结合前一段时间接触到的其他一些案例,看到很多人对一些新函数的使用方法有些值得商榷的地方,就打算一起介绍一下。

问题

问题很简单,

首先我们有这样一个参数表,记录了所有的产品信息,包括每个产品的名称,类别,单位成本和单价。

然后,我们要在某个地方做如下计算:

计算每个列出的类别的最高成本。

显然,在产品表中,每一个类别有多种产品,所以也就有多个单位成本。这里只要其中的最高的那个。

这个表设计的数据并不多,产品表只有1600多行,要计算的结果也只有2000多行。但是就有性能的问题。如果再有其他的公式,性能问题就很突出了。

方法讨论

这位朋友的文件中使用的公式实际上非常巧妙:

=MAX((Product!$B$2:$B$1691=B6)*(Product!$C$2:$C$1691))

这个公式是首先对产品表的Catetory列进行判断,是否等于当前的产品类别,得到一列TRUE/FALSE值,然后将这个结果与产品表的单位成本列相乘,这样,只有那些等于当前产品类别的行对应的单位成本保留下来,其余的都变成了0。

然后再求最大值。

用我之前演示过的工具测试一下,这一列的计算时间超过0.3秒,

这是一个很可观的时间了。考虑到这个计算只是总体计算中的一个很小的环节,跟其他公式叠加后,造成性能问题一点也不奇怪。

这个问题的常规解决方法其实应该用MAXIFS函数

=MAXIFS(Product!$C$2:$C$1691,Product!$B$2:$B$1691,Index!B6)

就是根据条件求最大值嘛,很简单(估计那位朋友应该是被其他公式带偏了思路,忘了这个公式了)。

用这种方法,计算速度得到一些提升,

有效果,但是不明显。

这里要强调一下,在很多实际场景中,这两种方法的速度差别要比这种测试环境种好很多。

继续找新方法。

仔细看这个问题,其实是个查找问题:

“查找类别等于当前类别的产品的最高单位成本”。

既然是个查找问题,就可以用VLOOKU函数

但是,同一个类别有多条记录,VLOOKUP返回找到的第一条记录。

所以,我们应该对产品表排序,按单位成本降序排序即可,保证找到的第一条一定是最高成本。

公式很简单,

=VLOOKUP(B6,Product!$B$2:$C$1691,2,0)

这个公式的效率很高,

当然,我们这列使用了精确匹配,如果使用近似匹配,效率还会有很多提升。不过需要按类别和单位成本两列进行(升序)排序。(这里就不演示了,关于近似匹配提升效率的方法,具体请参见这里

扩展 – 使用新函数要当心

这个问题本身基本解决了。接下来是扩展的内容。

有时候,参数表不能排序(比如,有另外更重要的计算中需要参数表的特定顺序),上面的VLOOKUP方法就行不通了。

幸亏Excel推出了SORT函数

于是,很多人就会使用下面的公式,

计算最大值的各种方法的讨论 ,也谈一谈新函数的使用方式 – Power Excel

=VLOOKUP(B6, SORT(Product!$B$2:$C$1691, {1,2},{1,-1}),2,0)

其中,SORT函数对Product表排序,按照类别和成本列,一个升序一个降序。结果数组作为数据源,使用VLOOKUP进行查找。

这个公式在效果上与我们前面介绍的最后一种VLOOKUP方法一样。

但是在计算效率上,这个公式表现很差,

这么简单的计算竟然需要2秒多。

甚至连近似匹配都挽救不了,

=VLOOKUP(B6, SORT(Product!$B$2:$C$1691, {1,2},{1,1}),2,1)

这个公式与上一个公式类似,只不过改用近似匹配。当然排序也必须都是升序。

看一看它的计算时间,

甚至比精确匹配还差!

我们说过,LET函数可以提高计算速度,于是将上面的公式改写下面的LET公式,

=LET(    prod, Product!$B$2:$C$1691,    sortedProd, SORT(prod, {1,2}, {1,-1}),    VLOOKUP(B6, sortedProd, 2, 0))

这个公式计算速度也没有丝毫改进,

这里一定要注意,有很多新函数都是在进行循环迭代,比如SORT函数,比如FILTER函数,还有那些循环函数。

一次性的使用基本问题不大,但是在需要反复使用的公式种嵌套使用,很可能就会造成严重的性能问题。

是不是没办法了呢?

有,结合动态数组就可以改进。

比如下面的公式,

=LET(    prod, Product!$B$2:$C$1691,    sortedProd, SORT(prod, {1,2}, {1,-1}),    VLOOKUP(B6:B2821, sortedProd, 2, 0))

这个公式看上去跟前一个公式基本一样,只是VLOOKUP的查找条件从B6变成了B6:B2821,从一个单元格变成了一个区域。

性能提升是巨大的,

原因很简单,耗时的排序才做从前一个公式种的2800多次,变成了只执行1次。