mar*_*ick 13 excel permutation excel-formula excel-lambda
这是一个经常被问到和回答的问题:如何在 Excel 中生成所有排列:
2011 2016 2017 2017 超级用户 2018 2021
现在到了2022 年,在它作为重复项关闭之前没有得到答案,这是不幸的,因为LAMBDA确实改变了这个问题的回答方式。
我很少有同样的需求,并且因不得不重新发明一个复杂的轮子而感到沮丧。因此,我将重新提出问题并在下面给出我自己的答案。我不会将任何提交标记为答案,但会邀请好的想法。我确信我自己的方法可以改进。
我正在尝试仅使用公式在 Excel 中创建循环。我想要实现的目标如下所述。假设我有 3 列作为输入: (i) 国家/地区;(ii) 变量;(iii) 年份。我想从这些输入进行扩展,然后为这些参数分配值。
输入:
| 国家 | 多变的 | 年 |
|---|---|---|
| 国标 | 国内生产总值 | 2015年 |
| 德 | 区域 | 2016年 |
| CH | 区域 | 2015年 |
输出:
| 国家 | 多变的 | 年 |
|---|---|---|
| 国标 | 国内生产总值 | 2015年 |
| 国标 | 国内生产总值 | 2016年 |
| 国标 | 区域 | 2015年 |
| 国标 | 区域 | 2016年 |
| 德 | 国内生产总值 | 2015年 |
| 德 | 国内生产总值 | 2016年 |
| 德 | 区域 | 2015年 |
| 德 | 区域 | 2016年 |
如何使用 Excel 有效地做到这一点?
我有三列,每一列都有不同类型的主数据,如下所示:
现在,我想要这三个单元格的所有可能组合 - 就像
aa kk jj
aa kk ff
aa ll jj
aa ll ff
aa mm jj
...
Run Code Online (Sandbox Code Playgroud)
这可以用公式来完成吗?我发现一个公式有 2 列,但我无法正确地将其扩展到 3 列
包含 2 列的公式:
=IF(ROW()-ROW($G$1)+1>COUNTA($A$2:$A$15)*COUNTA($B$2:$B$4),"",
INDEX($A$2:$A$15,INT((ROW()-ROW($G$1))/COUNTA($B$2:$B$4)+1))&
INDEX($B$2:$B$4,MOD(ROW()-ROW($G$1),COUNTA($B$2:$B$4))+1))
Run Code Online (Sandbox Code Playgroud)
其中 G1 是放置结果值的单元格
它们的共同点是它们都试图从一组有序的符号中创建一组有序的排列。它们都恰好需要 3 级符号,但 2018 年的问题是请求帮助从 2 级到 3 级,2021 年的问题是要求从 3 级到 5 级。2022 年的问题只是要求 3 级,但是输出需要是一个表。
如果我们像这样上升到 6 个级别会怎样?
| L1 | L2 | L3 | L4 | L5 | L6 |
|---|---|---|---|---|---|
| A | F | K | 磷 | U | 1 |
| 乙 | G | L | 问 | V | 2 |
| C | H | 右 | 瓦 | 3 | |
| D | X | 4 | |||
| 乙 |
这将生成 1'440 种排列。
| L1 | L2 | L3 | L4 | L5 | L6 |
|---|---|---|---|---|---|
| A | F | K | 磷 | U | 1 |
| A | F | K | 磷 | U | 2 |
| A | F | K | 磷 | U | 3 |
| A | F | K | 磷 | U | 4 |
| A | F | K | 磷 | V | 1 |
| A | F | K | 磷 | V | 2 |
| A | F | K | 磷 | V | 3 |
| A | F | K | 磷 | V | 4 |
| A | F | K | 磷 | 瓦 | 1 |
| ... | ... | ... | ... | ... | ... |
制定一个包含任意数量级别(列)的通用公式很困难。只需浏览一下提供的答案即可 - 它们每个都需要一些火箭科学,到目前为止,所有解决方案都对符号列的数量进行了硬编码限制。那么LAMBDA能否给我们一个通用的解决方案呢?
很酷的问题和脑筋急转弯;我只是对正在使用的东西感到困惑MAKEARRAY():
选项1:
您所说的“超级低效”是在计算行^列时创建排列列表。我认为下面的方法并没有那么低效。让我们想象一下以下情况:
公式为E1:
=LET(A,A1:C3,B,ROWS(A),C,COLUMNS(A),D,B^C,E,UNIQUE(MAKEARRAY(D,C,LAMBDA(rw,cl,INDEX(IF(A="","",A),MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1,cl)))),FILTER(E,MMULT(--(E<>""),SEQUENCE(C,,,0))=C))
Run Code Online (Sandbox Code Playgroud)
简而言之,它的作用是:
INDEX()s 来返回所有值。为此,我们需要行和列的正确索引。MAKEARRAY()由于 lambda 带来的递归功能,将使计算相对容易。在这些函数内部,其基本数学运算返回这些行和列的正确索引。事实上,列不需要计算,因为我们只是引用“cl”,并且所有行索引的所有计算都是通过 完成的MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1。UNIQUE()很少的资源来过滤掉任何潜在的重复项并将潜在的空行限制为单个空行。FILTER()并MMULT()很好地协同工作,过滤掉任何不需要的结果(读;空)。这是我认为我能得到的那样紧凑和快速。该公式现在适用于任何连续的单元格范围。单个单元格、单行、单列或任何二维范围。
选项2:
OP 正确地提到,选项 1 可能会在开始时创建太多元组,以至于稍后才丢弃它们。这可能效率低下。为了解决这个问题(如果这不是您想要的),我们可以使用更大的公式。让我们想象一下以下数据:
| A | 乙 | C |
|---|---|---|
| A | d | F |
| 乙 | e | H |
| e | ||
| C | G | |
| G |
我们看到有空单元格和重复值。这些就是选项 1 创建过多元组的原因。为了反驳这一点,我想出了一个更长的公式:
=LET(A,A1:C5,B,ROWS(A),C,COLUMNS(A),D,IF(A="",NA(),A),E,MAKEARRAY(B,C,LAMBDA(rw,cl,INDEX(SORT(INDEX(D,0,cl)),rw))),F,BYCOL(E,LAMBDA(cl,COUNTA(FILTER(cl,NOT(ISERROR(cl)))))),G,MAKEARRAY(PRODUCT(F),C,LAMBDA(rw,cl,INDEX(E,MOD(CEILING(rw/IFERROR(PRODUCT(INDEX(F,SEQUENCE(C-cl,,cl+1))),1),1)-1,INDEX(F,cl))+1,cl))),UNIQUE(G))
Run Code Online (Sandbox Code Playgroud)
分解一下:
LET()- 使用变量;A- 我们最初的全范围细胞(连续);B- A的总行数;C- A的列总数;D- 该公式IF(A="",NA(),A)旨在检查矩阵中的每个值是否为空(字符串)。如果是这样,请将其设为错误(这将在下一步中有意义)。E- 在此步骤中,公式MAKEARRAY(B,C,LAMBDA(rw,cl,INDEX(SORT(INDEX(D,0,cl)),rw)))对每一列进行排序,以便值位于顶部,所有错误均向下推:| A | 乙 | C |
|---|---|---|
| A | d | F |
| 乙 | e | G |
| C | e | G |
| #不适用 | #不适用 | H |
| #不适用 | #不适用 | #不适用 |
F- 此变量的公式BYCOL(E,LAMBDA(cl,COUNTA(FILTER(cl,NOT(ISERROR(cl))))))现在将计算每列的项目数量。这是稍后使用和计算所有排列所必需的。这个具体案例的结果将是{3;3;4}。G- 最后一个变量(如果选择这样使用它)使用MAKEARRAY(PRODUCT(F),C,LAMBDA(rw,cl,INDEX(E,MOD(CEILING(rw/IFERROR(PRODUCT(INDEX(F,SEQUENCE(C-cl,,cl+1))),1),1)-1,INDEX(F,cl))+1,cl))). 虽然很长,但每一步都很有意义;获取乘积(所有可能的排列)来计算行总数,列保持不变。在 中,LAMBDA()我们引用变量中当前列索引之后的所有列F。这是一个相当大的块需要消化,不幸的是我解释得不够好=)。UNIQUE(G)- 最后一步是过滤掉所有双重排列(如果有人选择的话)。结果:
现在,尽管选项 1 在可读性方面胜过选项 2,但第二个选项(经过非常有限的测试)计算时间仅为第一个选项的三分之一。因此,就速度而言,第二种选择是首选。
作为第二个选项的替代方案,我首先有:
=LET(A,A1:C5,B,ROWS(A),C,COLUMNS(A),D,MAKEARRAY(B,C,LAMBDA(rw,cl,IF(MATCH(INDEX(A,rw,cl),INDEX(A,0,cl),0)=rw,INDEX(A,rw,cl),NA()))),E,MAKEARRAY(B,C,LAMBDA(rw,cl,INDEX(SORT(INDEX(D,0,cl)),rw))),F,BYCOL(E,LAMBDA(cl,COUNTA(UNIQUE(FILTER(cl,NOT(ISERROR(cl))))))),G,MAKEARRAY(PRODUCT(F),C,LAMBDA(rw,cl,INDEX(E,MOD(CEILING(rw/IFERROR(PRODUCT(INDEX(F,SEQUENCE(C-cl,,cl+1))),1),1)-1,INDEX(F,cl))+1,cl))),G)
Run Code Online (Sandbox Code Playgroud)
现在,这会将D变量更改为更长的公式,以预先删除每列中的重复项。两种变体都可以很好地工作。
对于 2022 年的问题,我使用了以下 LET:
\n=LET( matrix, A2:E6,\n\n cC, COLUMNS( matrix ), cSeq, SEQUENCE( 1, cC ),\n rC, ROWS( matrix ), rSeq, SEQUENCE( rC ),\n eC, rC ^ cC, eSeq, SEQUENCE( eC,,0 ),\n unblank, IF( ISBLANK(matrix), "\xc2\xb0|\xc2\xb0", matrix ),\n m, UNIQUE( INDEX( unblank, MOD( INT( INT( SEQUENCE( eC, cC, 0 )/cC )/rC^SEQUENCE( 1, cC, cC-1, -1 ) ), rC ) + 1, cSeq ) ),\n FILTER( m, BYROW( IFERROR( FIND( "\xc2\xb0|\xc2\xb0", m ), 0 ), LAMBDA(x, SUM( x ) ) ) = 0 ) )\nRun Code Online (Sandbox Code Playgroud)\n\n此公式插入“\xc2\xb0|\xc2\xb0”来代替空格,以避免将空格重新转换为 0。它通过应用 UNIQUE 来消除重复项。
\n这是非常低效的,因为它会生成所有可能的排列,包括重复,然后将它们过滤掉。对于小规模矩阵来说,这不是什么大问题,但想象一下 100 行 x 3 列矩阵将生成 1\'000\'000 行,然后将它们过滤为一个小子集。
\n然而,有一个小好处,请注意黄色单元格中的f,它滞留在列的中间,并且与其他单元格不相邻。这个公式可以像冠军一样处理这个问题。它只是将其集成到有效排列的输出中。这在下面的有效公式中很重要。
\n至于一般的 LAMBDA 公式,我使用:
\nSYMBOLPERMUTATIONS =\nLAMBDA( matrix,\n\nLET(\n cC, COLUMNS( matrix ),\n cSeq, SEQUENCE( 1, cC ),\n symbolCounts, BYCOL( matrix, LAMBDA(x, SUM( --NOT( ISBLANK( x ) ) ) ) ),\n rSeq, SEQUENCE( MAX( symbolCounts )-1 ),\n permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 ),\n permMods, IFERROR( INDEX( permFactors,, IF( cSeq + 1 > cC, -1, cSeq+1 ) ), 1 ),\n idx, INT( MOD( SEQUENCE( INDEX(permFactors, 1, 1),,0 ), permFactors )/permMods ) + 1,\n answer, INDEX( matrix, idx, cSeq ),\n er, OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) ), // detect if there are stranded symbols\n IF( SUM(symbolCounts)=0, "no symbols",\n IF( er, "symbol columns must be contiguous",\n answer ) ) )\n\n)\nRun Code Online (Sandbox Code Playgroud)\n\n这需要矩阵,就像上面一样(LET 版本如下所示)。它提供了完全相同的结果,但存在显着差异:
\n5^6 = 15625排列以得出1440有效的排列。这仅生成需要的内容,不需要过滤。因此,需要进行错误检测和处理。变量er使用此 LAMBDA Helper 检测矩阵中是否有任何与上面的符号不按列连续的滞留符号:
\nOR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) )\nRun Code Online (Sandbox Code Playgroud)\n\n\n但这带来了新的挑战,甚至可能是一个全新的问题。在搁浅的f的情况下,任何人都可以想出一种方法来合并它吗?
\n
另一个错误陷阱是检测列是否完全为空。
\nLAMBDA 带来的魔力来自这一行,它使两个公式都可以扩展到任意数量的列,而无需对其进行硬编码:
\npermFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 )\nRun Code Online (Sandbox Code Playgroud)\n我从JvdV对这个问题的回答中得到了这个答案,该答案专门旨在尝试解决这个问题。Scott Craner和Bosco Yip已经证明,如果没有 LAMBDA,这个问题基本上是无法解决的,而 JvdV 则展示了如何使用 LAMBDA 助手来解决这个问题SCAN。
=LET( matrix, A2:F6,\n cC, COLUMNS( matrix ),\n cSeq, SEQUENCE( 1, cC ),\n symbolCounts, BYCOL( matrix, LAMBDA(x, SUM( --NOT( ISBLANK( x ) ) ) ) ),\n rSeq, SEQUENCE( MAX( symbolCounts )-1 ),\n permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 ),\n permMods, IFERROR( INDEX( permFactors,, IF( cSeq + 1 > cC, -1, cSeq+1 ) ), 1 ),\n idx, INT( MOD( SEQUENCE( INDEX(permFactors, 1, 1),,0 ), permFactors )/permMods ) + 1,\n answer, INDEX( matrix, idx, cSeq ),\n er, OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) ),\n IF( SUM(symbolCounts)=0, "no symbols",\n IF( er, "symbol columns must be contiguous",\n answer ) ) )\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
2729 次 |
| 最近记录: |