9

这是一个经常被问到和回答的问题:如何在 Excel 中生成所有排列

2011 2016 2017 2017 超级用户 2018 2021

现在在2022 年,它在作为副本关闭之前没有得到答案,这很不幸,因为LAMBDA确实改变了回答这个问题的方式。

我很少有同样的需求,并且因为不得不重新发明一个复杂的轮子而感到沮丧。所以,我将重新提出这个问题,并在下面给出我自己的答案。我不会将任何提交的内容标记为答案,但会邀请好的想法。我确信我自己的方法可以改进。

重申 2022 年的问题

我正在尝试仅使用公式在 excel 中创建一个循环。我想要达到的目标如下所述。假设我有 3 列作为输入:(i)国家;(ii) 可变的;(iii) 年。我想从这些输入扩展,然后为这些参数赋值。

输入:

国家 多变的
国标 国内生产总值 2015
区域 2016
甲烷 区域 2015

输出:

国家 多变的
国标 国内生产总值 2015
国标 国内生产总值 2016
国标 区域 2015
国标 区域 2016
国内生产总值 2015
国内生产总值 2016
区域 2015
区域 2016

如何使用 Excel 有效地做到这一点?

扩展 2018 年的问题

我有三列,每一列都有不同类型的主数据,如下所示:

在此处输入图像描述

现在,我想要这三个单元格的所有可能组合 - 比如

aa kk jj
aa kk ff
aa ll jj
aa ll ff
aa mm jj
...

这可以用公式来完成吗?我找到了一个包含 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))

其中 G1 是放置结果值的单元格

共同要求

它们的共同点是它们都试图从一组有序的符号中创建一组有序的排列。他们都恰好需要3级符号,但是2018年的问题是寻求帮助从2级到3级,2021年的问题是要求从3级到5级。2022年的问题只是要求3级,但是输出需要是一个表格。

如果我们像这样上升到 6 个级别怎么办?

L1 L2 L3 L4 L5 L6
一个 F ķ ü 1
G 大号 2
C H R W 3
D X 4

这将产生 1'440 个排列。

L1 L2 L3 L4 L5 L6
一个 F ķ ü 1
一个 F ķ ü 2
一个 F ķ ü 3
一个 F ķ ü 4
一个 F ķ 1
一个 F ķ 2
一个 F ķ 3
一个 F ķ 4
一个 F ķ W 1
... ... ... ... ... ...

制作一个包含任意数量的级别(列)的通用公式很困难。只需查看提供的答案 - 他们每个人都需要一些火箭科学,直到现在,所有解决方案都对符号列数进行了硬编码限制。那么LAMBDA 能否给我们一个通用的解决方案呢?

4

3 回答 3

7

很酷的问题和脑洞;我只是对正在使用的东西感到困惑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))

简而言之,这是做什么的:

  • 变量 AD 都是助手。
  • 然后的想法是只使用 simple INDEX()s 来返回所有值。为此,我们需要为行和列提供正确的索引。
  • MAKEARRAY()由于 lambda 带来的递归功能,将使计算相对容易。在这些函数内部,它的基本数学运算可以为这些行和列返回正确的索引。事实上,列不需要计算,因为我们只是引用 'cl' 并且所有行索引的所有计算都是通过MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1.
  • 上述结果被投入UNIQUE()使用,以使用非常少的资源来过滤掉任何潜在的重复项并将潜在的空行限制为单个空行。
  • FILTER()MMULT()很好地协同工作以过滤掉任何不需要的结果(读取;空)。

这就像我想的那样紧凑和快速。该公式现在将适用于任何连续的单元格范围。单个单元格、单行、单列或任何二维范围。


选项 2:

OP 正确地提到选项 1 可能会在开始时创建太多元组,以便以后丢弃它们。这可能是低效的。为了解决这个问题(如果这不是你想要的),我们可以使用一个更大的公式。让我们想象以下数据:

一个 C
一个 d F
b 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))

要打破这一点:

  • 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)))对每一列进行排序,因此值在顶部,所有错误都被向下推:
一个 C
一个 d F
b 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)

现在,这会将D变量更改为更长的公式,以预先删除每列中的重复项。两种变体都可以正常工作。

于 2022-02-19T22:33:08.160 回答
5

LAMBDA 支持的简单 LET

对于 2022 年的问题,我使用了以下 LET:

=LET( matrix, A2:E6,

   cC, COLUMNS( matrix ), cSeq, SEQUENCE( 1, cC ),
   rC, ROWS( matrix ), rSeq, SEQUENCE( rC ),
   eC, rC ^ cC, eSeq, SEQUENCE( eC,,0 ),
   unblank, IF( ISBLANK(matrix), "°|°", matrix ),
   m, UNIQUE( INDEX( unblank, MOD( INT( INT( SEQUENCE( eC, cC, 0 )/cC )/rC^SEQUENCE( 1, cC, cC-1, -1 ) ), rC ) + 1, cSeq ) ),
   FILTER( m, BYROW( IFERROR( FIND( "°|°", m ), 0 ), LAMBDA(x, SUM( x ) ) ) = 0 ) )

其中用于在 A2:E6 中生成排列的矩阵: 在此处输入图像描述

此公式插入“°|°”代替空白,以避免将空白重铸为 0。它通过应用 UNIQUE 来消除重复项。

这是超级低效的,因为它会生成所有可能的排列,包括重复,然后将它们过滤掉。对于小规模矩阵,这没什么大不了的,但想象一下,一个 100 行乘 3 列的矩阵会生成 1'000'000 行,然后将它们过滤到一个小的子集。

但是,有一个小好处,请注意黄色单元格中的f,它被困在列的中间并且与它的同行不相邻。这个公式像冠军一样处理。它只是将其集成到有效排列的输出中。这在下面的有效公式中很重要。

由 LAMBDA 支持的高效通用 LET

至于一般的 LAMBDA 公式,我使用了:

SYMBOLPERMUTATIONS =
LAMBDA( matrix,

LET(
       cC, COLUMNS( matrix ),
       cSeq, SEQUENCE( 1, cC ),
       symbolCounts, BYCOL( matrix, LAMBDA(x, SUM( --NOT( ISBLANK( x ) ) ) ) ),
       rSeq, SEQUENCE( MAX( symbolCounts )-1 ),
       permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 ),
       permMods, IFERROR( INDEX( permFactors,, IF( cSeq + 1  > cC, -1, cSeq+1 ) ), 1 ),
       idx, INT( MOD( SEQUENCE( INDEX(permFactors, 1, 1),,0 ), permFactors )/permMods ) + 1,
       answer, INDEX( matrix, idx, cSeq ),
       er, OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) ), // detect if there are stranded symbols
       IF( SUM(symbolCounts)=0, "no symbols",
             IF( er, "symbol columns must be contiguous",
                          answer ) ) )

)

在此处输入图像描述

这需要矩阵,就像上面一样(LET 版本如下所示)。它提供了完全相同的结果,但存在显着差异:

  • 它是有效的。在此处显示的示例中,上面的简单公式 将生成5^6 = 15625排列以达到 1440有效的排列。这只会生成需要的内容,不需要过滤。
  • 但是,它根本无法处理那个搁浅的f。事实上,它会在f的位置生成一个 0,这不是拥有大量排列的用户甚至可能注意到的。

因此,存在错误检测和处理。变量er使用此 LAMBDA Helper 检测矩阵中是否有任何与上面的列不连续的滞留符号:

OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) )

但这创造了一个新的挑战,也许是一个全新的问题。在搁浅f的情况下,任何人都可以想出一种方法来合并它吗?

另一个错误陷阱是检测列是否完全为空。

兰姆达魔术

LAMBDA 带来的魔力来自这一行,它使两个公式都可以扩展到任意数量的列,而无需对它们进行硬编码:

permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 )

我从JvdV对这个问题的回答中得到了这个,该问题专门针对试图解决这个问题Scott CranerBosco Yip已经证明,没有 LAMBDA 基本上是无法解决的,而 JvdV 展示了如何使用 LAMBDA 助手来完成SCAN

LET 版本的有效公式

=LET( matrix, A2:F6,
   cC, COLUMNS( matrix ),
   cSeq, SEQUENCE( 1, cC ),
   symbolCounts, BYCOL( matrix, LAMBDA(x, SUM( --NOT( ISBLANK( x ) ) ) ) ),
   rSeq, SEQUENCE( MAX( symbolCounts )-1 ),
   permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 ),
   permMods, IFERROR( INDEX( permFactors,, IF( cSeq + 1  > cC, -1, cSeq+1 ) ), 1 ),
   idx, INT( MOD( SEQUENCE( INDEX(permFactors, 1, 1),,0 ), permFactors )/permMods ) + 1,
   answer, INDEX( matrix, idx, cSeq ),
   er, OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) ),
   IF( SUM(symbolCounts)=0, "no symbols",
         IF( er, "symbol columns must be contiguous",
                      answer ) ) )
于 2022-02-19T21:21:30.717 回答
3

也许是一个迭代 LAMBDA

MyLambda在名称管理器中定义为:

=LAMBDA(n,LET(Rng,Sheet1!$A$1:$D$3,α,COLUMNS(Rng),β,INDEX(Rng,,n),γ,FILTER(β,β<>""),IF(n>α,"",LET(δ,γ&REPT(CHAR(32),25)&TRANSPOSE(MyLambda(n+1)),ε,COLUMNS(δ),ζ,SEQUENCE(ROWS(δ)*ε,,0),INDEX(δ,1+QUOTIENT(ζ,ε),1+MOD(ζ,ε))))))

之后我们可以调用

=LET(Rng,Sheet1!$A$1:$D$3,α,COLUMNS(Rng),TRIM(MID(MyLambda(1),25*SEQUENCE(,α,0)+1,25)))

工作表内。

请注意,Rng中的每一列必须至少包含一个条目

其背后的逻辑LAMBDA迭代地(通过调用MyLambdaMyLambda(n+1) )连接范围内的连续列,基于两个正交数组的连接生成一个二维数组,该二维数组包含这两个数组的元素的所有排列。

={"Dave";"Mike"}&TRANSPOSE({"Bob";"Steve";"Paul"})

例如,生成

{"DaveBob","DaveSteve","DavePaul";"MikeBob","MikeSteve","MikePaul"}

然后需要将其重新调整为一维水平数组,

{"DaveBob";"DaveSteve";"DavePaul";"MikeBob";"MikeSteve";"MikePaul"}

这样它可以与范围内的下一个(垂直,正交)列迭代连接。等等。

第二个函数在工作表中调用,然后将连接的数组解析为所需的行数/列数。

于 2022-02-21T07:39:14.673 回答