ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 1685|回复: 25

[分享] 递归替换REDUCE某类循环方式的一种方法(完结)

[复制链接]

TA的精华主题

TA的得分主题

发表于 2024-6-6 13:28 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 shaowu459 于 2024-6-6 14:13 编辑

本帖示例比较简单,没有什么新鲜的公式,发本帖主要有以下三个出发点:

1)一是REDUCE函数在循环过程中没有达到条件就退出循环的机制,目前一般是用IF(条件判断,x,循环运算)的方式来实现当满足某些条件的时候,保留x不变,这种方式REDUCE还是要循环二参那么多次,使用递归可以达到满足条件即退出,减少循环次数。本帖仅就该点再次做个提示。

2)在Microsoft 365:X檔案大揭秘帖子里,我列示了我自己常用的一种解决问题的思路(在2楼具体说明),如果只用REDUCE循环,在堆积、提取、判断位置的时候会遇到些麻烦,增加公式的复杂成都,使用递归就可以很好的解决这个问题。标题所说,也就是使用递归代替这种循环解决某类问题的思路。

3)之前发的很多帖子,帖子文字都是在做了很多类型题目之后总结概括的内容,虽然有附例题,但是总归有些不太容易被初学者理解。因此本帖也想给之前发的LAMBDA函数:递归基础下面截图这段话增加示例进行说明,以使其更方便理解。
图片.jpg

递归替换REDUCE某类循环方式的一种方法.rar

14.78 KB, 下载次数: 116

评分

14

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 13:40 | 显示全部楼层
之前在x档案大揭秘中提到过这样一个解题方法,有如下一个原始数组(可能是单行、单列或多行、多列):
图片.png
要求每次从数组中从上到下提出出若干行(例如经典的分组问题),通过重组、计算等方式组合生成一个新的结果,使用过的行不再使用,直到原数组所有行都被用过为止。


实际公式书写大概是下面的逻辑。假设原数组前2行符合第一次提取条件,那么提取前2行生成一个结果,这个结果可能是单值也可能是多行列的结果,堆积在原数组下方,也就是新数组=VSTACK(X,前2行生成结果),然后再将新数组前2行DROP掉。

图片.png


下次循环的时候,从新数组的顶端开始判断,比如说提取前3行生成一个结果堆积在上一步的数组下方,再把结果数组前3行DROP掉。这样一直循环直到原数组所有行的被使用过为止。

图片.png


按上面的方式循环,需要增加一些提取和判断,包括原数组和新数组的分界标识、错误值和边界值的处理等等,稍微麻烦一些。而在递归中,我们可以使用一个参数arr单独存储原数组及逐渐DROP行的数组,一个参数res存储新生成结果的数组。什么时候退出呢?等arr返回错误值的时候,也就是所有行都被DROP掉的时候,让递归返回res结果即可。


TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 13:57 | 显示全部楼层
本帖最后由 shaowu459 于 2024-6-6 14:00 编辑

示例1:将一列数据分组,标准是累计和≤120就分一组。


图片.png

因为要从上到下累计求和,所以我们很自然可以想到使用SCAN函数实现。
图片.png

然后可以使用XMATCH函数判断≤120的位置是第几个:
图片.png

有了位置2,提取数组的前2行进行合并存储即可。同时,把原数组前2行使用DROP函数去掉。

下一次循环的时候,同样使用SCAN函数对剩余的数组累计求和:
图片.png


同样判断≤120的位置,然后提取、合并、存储新结果、去掉数组的前3行:
图片.png


有了上面的分析,我们就可以使用递归来完成这个任务。使用arr代表原数组和变化后的数组,使用res存储每次循环的结果。参考公式如下(公式都不是最优的,仅用于清晰说明思路):
  1. =LET(f,LAMBDA(f,arr,res,LET(s,XMATCH(120,SCAN(,arr,SUM),-1),IF(COUNT(arr),f(f,DROP(arr,s),VSTACK(res,ARRAYTOTEXT(TAKE(arr,s)))),res))),f(f,A1:A14,"结果"))
复制代码
图片.png


公式简要说明如下:
=LET(
    f, LAMBDA(f,

              arr,   存储原数组和变化数组

              res,   存储结果
              LET(
                  s, XMATCH(
                            120, 匹配值
                            SCAN(, arr, SUM),   对arr累计求和
                           -1),   s结果为从上到下≤120的最大值所处位置
                  IF(
                     COUNT(arr),    如果arr是错误值时COUNT结果为0,也即可以退出循环
                     f(f,
                       DROP(arr, s), 递归重新调用f,新参数为上次的arr去掉前s行
                       VSTACK(res, ARRAYTOTEXT(TAKE(arr, s)))  堆积上一步的res和本次前s行的合并结果
                       ),
                     res   如果arr已经是错误值,则返回res结果

                    )
                 )
             ),
    f(f, A1:A14, "结果")
)

如果需要堆积成多行多列的结果,可以参考如下公式,堆积部分稍作调整即可:
  1. =LET(f,LAMBDA(f,arr,res,LET(s,XMATCH(120,SCAN(,arr,SUM),-1),IF(COUNT(arr),f(f,DROP(arr,s),VSTACK(res,TOROW(TAKE(arr,s)))),res))),IFNA(DROP(f(f,A1:A14,0),1),""))
复制代码

图片.png
图片.png

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 14:11 | 显示全部楼层
示例2:根据数量分组。

下图A列是名称,B列是数组,要求分组后生成右侧的结果样式。规则举例说明:第一行的Accessories共440个,2-6行对应的B列数量加起来等于440,则1-6行为一组。

图片.png


解题思路和示例1基本一样,大体思路如下:
1)将数组第2列累计求和;
2)查找数组第一个值的2倍在累计求和数组中的位置t;
3)提取出数组前t行得到数组u,也就是整个分组的数据;
4)根据u进行组合调整,得到结果样式,堆积在res下方;
5)再次递归调用,新数组arr为上一步的arr去掉前t行。
6)判断arr已经是错误值,则可退出递归,返回res。

参考公式如下:
  1. =LET(f,LAMBDA(f,arr,res,LET(s,DROP(arr,,1),t,MATCH(@s*2,SCAN(,s,SUM),),u,TAKE(arr,t),IF(COUNT(arr),f(f,DROP(arr,t),VSTACK(res,IFNA(HSTACK(@u,DROP(u,1)),@u))),res))),DROP(f(f,+A1:B10,0),1))
复制代码
图片.jpg
图片.png


公式简要说明如下:
=LET(
    f, LAMBDA(f, arr, res,
        LET(
            s, DROP(arr, , 1),    提取数组第2列的数值
            t, MATCH(@s * 2, SCAN(, s, SUM), ),   获取在s累计求和中第一个值2倍的数字所在位置
            u, TAKE(arr, t),  提取arr的前t行,也就是当前分组所有的行数
            IF(
                COUNT(arr),   判断数组中数字的个数,如果是错误值返回0,也即可退出循环
                f(
                    f,
                    DROP(arr, t),  再次循环时参数变成arr去掉前t行的结果
                    VSTACK(res, IFNA(HSTACK(@u, DROP(u, 1)), @u))  堆积res和当前分组行内容产生的结果
                ),
                res  arr是错误值时退出循环,返回res
            )
        )
    ),
    DROP(f(f, +A1:B10, 0), 1)
)

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2024-6-6 15:13 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 15:18 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
华尔街操盘手 发表于 2024-6-6 15:13
超人大佬,为啥我复制了你的公式,是报错的

一楼有文件,你可以直接下载,里面有公式。
另外,你应该不是直接复制的,在2*s那里,s前面还有个字符的,提取数组s的第一个值。

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2024-6-6 15:21 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
每天学习,每天进一小步。。。。

TA的精华主题

TA的得分主题

发表于 2024-6-6 18:56 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
本帖最后由 edwin11891 于 2024-6-6 18:59 编辑
shaowu459 发表于 2024-6-6 13:57
示例1:将一列数据分组,标准是累计和≤120就分一组。

如果前面加一列,对每行数据求和(证实<120)呢,公式该如何修改?
微信图片_20240606185820.png

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 20:03 来自手机 | 显示全部楼层
edwin11891 发表于 2024-6-6 18:56
如果前面加一列,对每行数据求和(证实

开始想过放合计,不过作为示例弄得太麻烦就不好了。就是在vstack第二参数堆积的地方改改,横向堆一个结果

TA的精华主题

TA的得分主题

 楼主| 发表于 2024-6-6 20:06 来自手机 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
edwin11891 发表于 2024-6-6 18:56
如果前面加一列,对每行数据求和(证实

没带电脑,就是后面这里VSTACK(res,hstack(sum(TAKE(arr,s)),TOROW(TAKE(arr,s))))
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-26 16:35 , Processed in 0.057271 second(s), 12 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表