分享

Excel函数循环之For循环的遗留问题- Excel函数式编程

 ExcelEasy 2024-07-09 发布于北京


上次我们介绍了使用Excel函数实现for循环(Excel函数循环之For循环 - Excel函数式编程),最后我们留了两个问题:

  1. 当时我们只考虑了返回一个数组的情况,所以使用了SCAN函数。如果要返回一个值,就需要将SCAN修改为REDUCE函数。
    如何才能修改自定义函数,使之可以根据需要自行选择返回数组还是数值呢?

  2. 还有另外一种实现For循环的思路:首先根据循环条件(from,to,step),我们可以确定数据data中需要用到的行。我们就可以使用FILTER函数来筛选满足条件的行,然后对筛选后的结果使用BYROW函数循环。
    这种方式是否可以代替我们这里的实现方案?

先看第一个问题。

自行选择SCAN和 REDUCE

原来的函数定义是这样的:

/**For循环= ForLoop(data, from, to, step,init_value, func)*/ForLoop = LAMBDA(    data,   // 数据区域或数组    from,   // 其实索引号    to,     // 结束索引号    step,   // 步长    init_value, // 循环初值    func,   // 循环处理函数    LET(        loopArr, SEQUENCE((to - from) / step + 1,,from, step),  // 索引数组,比如,from = 1, to = 5, step = 2,就会生成一个{1;3;5}的索引数组        SCAN(   //采用SCAN,返回一个数组            init_value,             loopArr,    // 对于索引数组进行循环            LAMBDA(acc, i, func(acc, i, data))  // 使用循环处理函数参数进行数据处理        )    ));

第13行使用了SCAN。这里可以使用REDUCE函数来代替。

我们的目标是在使用“For循环”时自行决定使用哪个函数,就需要讲这个选择作为这个For循环函数的参数。

所以我们增加一个参数:

ForLoop1 = LAMBDA(    scan_or_reduce, //scan or reduce    data,   // 数据区域或数组    from,   // 其实索引号    to,     // 结束索引号    step,   // 步长    init_value, // 循环初值    func,   // 循环处理函数

第一行就是新增加的参数,如果希望返回数组,调用时,这里就写scan,如果希望返回数值,这里就写reduce。

其余的参数没有变化。

函数体中只要将原来的SCAN换成这个参数(scan_or_reduce)即可,

/**For循环= ForLoop1(scan_or_reduce, data, from, to, step,init_value, func)*/ForLoop1 = LAMBDA(    scan_or_reduce, //scan or reduce    data,   // 数据区域或数组    from,   // 其实索引号    to,     // 结束索引号    step,   // 步长    init_value, // 循环初值    func,   // 循环处理函数    LET(        loopArr, SEQUENCE((to - from) / step + 1,,from, step),  // 索引数组,比如,from = 1, to = 5, step = 2,就会生成一个{1;3;5}的索引数组        scan_or_reduce(   //采用SCAN,返回一个数组            init_value,             loopArr,    // 对于索引数组进行循环            LAMBDA(acc, i, func(acc, i, data))  // 使用循环处理函数参数进行数据处理        )    ));

第14行的就是用了这个参数。

调用时,在第一个参数中写SCAN,

返回的就是数组。

如果写REDUCE,返回的就是一个数值,

可以使用BYROW代替吗?

在这个自定义函数中,

/**For循环= ForLoop1(scan_or_reduce, data, from, to, step,init_value, func)*/ForLoop1 = LAMBDA(    scan_or_reduce, //scan or reduce    data,   // 数据区域或数组    from,   // 其实索引号    to,     // 结束索引号    step,   // 步长    init_value, // 循环初值    func,   // 循环处理函数    LET(        loopArr, SEQUENCE((to - from) / step + 1,,from, step),  // 索引数组,比如,from = 1, to = 5, step = 2,就会生成一个{1;3;5}的索引数组        scan_or_reduce(   //采用SCAN,返回一个数组            init_value,             loopArr,    // 对于索引数组进行循环            LAMBDA(acc, i, func(acc, i, data))  // 使用循环处理函数参数进行数据处理        )    ));

是否可以使用BYROW代替这里的SCAN或者REDUCE呢?

从案例中我们引用这个函数的方式来看,

=ForLoop1(    REDUCE,    A2:B5,    1,    4,    2,    "",    LAMBDA(acc,i,data, acc & INDEX(data, i, 1)))

第8行中我们使用了INDEX(data,i,1)来返回数据其余中的一行,然后交给第8行定义的lambda函数做处理。

这么说,完全可以用BYROW函数来代替,反正我们只是逐行处理。

这样的话,我们甚至都不用第行这个自定义函数,这里只需要写一个BYROW就可以了。

不过我们不建议这么做。

因为BYROW只能逐行处理,它没有REDUCE函数那种返回最后一行的功能。

另外,SCAN/REDUCE这两个函数还有一个累加器功能,尽管我们在示例中没有使用,但是必要时它们可以发挥巨大作用。BYROW就没有这个能力。

也有朋友在之前的For循环文章下留言,问既然没有用到acc这个参数,为什么不用MAP函数

原因也是一样的。SCAN/REDUCE函数的适用范围更广,如果不使用累加器功能,它就像普通的MAP一样,但是必要时,累加器就可以起到大作用。

所以,为了应用范围更广,建议使用SCAN/REDUCE函数。

最后

最后还有一个问题:

能否使用这个“For循环函数”进行双重循环,类似于下面这种形式:

ForLoop1(ForLoop1(......)......)

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多