今天介绍一个实际的Excel案例。 很多人有一个错误的印象:使用Excel函数式编程解决问题比以前单纯的使用Excel公式更难。其实这是一个很典型的误解。就绝大多数问题而言,使用函数式编程从思路到实现都更容易掌握。 而且,无论是使用传统的Excel公式,还是使用函数式编程,二者从思路上是一致的。但是因为在传统的解决方案中,你必须花费大量时间和精力去思考如何编写“巧妙”的公式,导致大部分人忽略了解决思路的重要性。 先来看具体的问题: 上表中记录了不同人员在不同月份的累计订购金额。 要求:针对每人,求出销售额首次发生变动的月份和金额。比如,对于B来说,202203是发生变动的月份,金额是72。 在解决这类问题时,第一件事是明确需求。上面的要求是经过极度简化的,据此设计的方案很可能不能满足问题提出者的真实需求。所以,我们需要给出最终的样表, 无论使用什么样的技术方案,第一件事是设计解决思路,而不是匆匆忙忙开始编写“巧妙”的公式。 我们将问题简化一下,只考虑1个用户的情形, 并没有特别简单的公式可以让我们从这一行数字中迅速发现第一个72。 但是,如果我们变化一下数据, 显然,后一份数据是根据前一份数据中后一个月份减去前一个月份得来。 现在,我们非常容易可以迅速定位第一个发生变化的数据了,因为它是整行数据中第一个不为0的数据。 很简单,就是一个简单的MATCH函数应用。 作为参考,这个公式可以这么写: =MATCH(TRUE,D13:N13<>0,0) 这个公式返回的是位置。根据这个位置,自然可以很容易得到想要的结果,只要一个简单的INDEX函数即可: 有了上面的思路,如果使用以前的公式思路进行解决,当然离不了辅助列和辅助表, 上图中演示了如何使用MATCH函数和INDEX函数完成这个需求。诀窍在于添加了辅助表A11:N18,以及辅助列O12:O18。 而使用函数式编程来解决这个问题,思路一模一样,却只要一个公式即可完成,不需要中间的辅助表和辅助列, =LET( srcdata, A1:N8, months, DROP(TAKE(srcdata, 1), , 1), Names, DROP(TAKE(srcdata, , 1), 1), dataArray, DROP(srcdata, 1, 1), changes, MAKEARRAY( ROWS(Names), 3, LAMBDA(r, c, LET( curRow, CHOOSEROWS(dataArray, r), prefix, DROP(curRow, , -1), suffix, DROP(curRow, , 1), diff, HSTACK(0, suffix - prefix), idx, MATCH(TRUE, diff <> 0, 0), IFS( c = 1, CHOOSECOLS(months, idx), c = 2, CHOOSECOLS(curRow, idx), c = 3, CHOOSECOLS(diff, idx) ) ) ) ), IFERROR(changes, "")) 这个公式看似复杂,但是逻辑清晰,简单,只要根据我们前面分析的思路逐步实现即可。首先,整个过程分为两步。红色部分是将数据区域进行划分,共分为三部分: months - 首行,即各月份; names - 首列,即用户姓名; dataArray - 即数据区域; 绿色部分是具体实现。整个过程是通过MAKEARRAY函数循环处理每一个用户,所以需要对names数组进行循环,生成3列,分别是首次改变日期,金额,以及变化的差额。 循环中,对当前循环到的用户,首先取出当前的数据,即curRow, 然后将前面11个月和后面11个月的数据分别取出,即prefix,suffix, 然后计算差额diff,suffix - prefix只有11个数,所以前面加上0 补足12个数, 然后用在diff中找到第一个不为0的位置, 然后通过IFS函数分别取出3列结果数据,其中的CHOOSECOLS函数可以用INDEX函数代替。 最后,我们再想得更多一些:如果我们找的不是首次发生变化,而是要找到所有发生变化的日期和金额, 即,需要得到如下的结果表格, 又应该怎么做呢? |
|