使用 LAMBDA 在 Excel 中生成所有排列

mar*_*ick 13 excel permutation excel-formula excel-lambda

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

2011 2016 2017 2017 超级用户 2018 2021

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

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

重申 2022 年问题

我正在尝试仅使用公式在 Excel 中创建循环。我想要实现的目标如下所述。假设我有 3 列作为输入: (i) 国家/地区;(ii) 变量;(iii) 年份。我想从这些输入进行扩展,然后为这些参数分配值。

输入:

国家 多变的
国标 国内生产总值 2015年
区域 2016年
CH 区域 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
...
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能否给我们一个通用的解决方案呢?

Jvd*_*vdV 8

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

简而言之,它的作用是:

  • 变量 AD 都是助手。
  • 然后的想法是只使用简单的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变量更改为更长的公式,以预先删除每列中的重复项。两种变体都可以很好地工作。

  • 这真是天才!`MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1` 是一件艺术品。您的版本集成了***搁浅f***而不是强制错误。然而,它的效率并不高,因为它仍然会生成整个矩阵的排列,因此 100X3 矩阵仍然会生成 1m 个元组。但是,通过避免链问题,可以在功能上获得很大的好处。我想到了一个想法:如果将最终的“FILTER”包装在“UNIQUE”中,则可以防止用户在一列符号中多次使用相同符号的情况下出现重复。出色的工作。 (2认同)

mar*_*ick 6

LAMBDA 支持的简单 LET

\n

对于 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 ) )\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

此公式插入“\xc2\xb0|\xc2\xb0”来代替空格,以避免将空格重新转换为 0。它通过应用 UNIQUE 来消除重复项。

\n

这是非常低效的,因为它会生成所有可能的排列,包括重复,然后将它们过滤掉。对于小规模矩阵来说,这不是什么大问题,但想象一下 100 行 x 3 列矩阵将生成 1\'000\'000 行,然后将它们过滤为一个小子集。

\n

然而,有一个小好处,请注意黄色单元格中的f,它滞留在列的中间,并且与其他单元格不相邻。这个公式可以像冠军一样处理这个问题。它只是将其集成到有效排列的输出中。这在下面的有效公式中很重要。

\n

LAMBDA支持的高效通用LET

\n

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

\n
SYMBOLPERMUTATIONS =\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)\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

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

\n
    \n
  • 它是有效的。在此显示的示例中,上面的简单公式将生成5^6 = 15625排列以得出1440有效的排列。这仅生成需要的内容,不需要过滤。
  • \n
  • 然而,它根本不处理那个搁浅的f。事实上,它会在f的位置生成一个 0 ,而具有大量排列的用户可能不会注意到这一点。
  • \n
\n

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

\n
OR( BYCOL( --ISBLANK(matrix), LAMBDA(x, SUM(--(INDEX(x,rSeq+1)<INDEX(x,rSeq))) ) ) )\n
Run Code Online (Sandbox Code Playgroud)\n
\n

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

\n
\n

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

\n

兰姆达魔法

\n

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

\n
permFactors, INDEX( SCAN( 1, INDEX( symbolCounts, , cC-cSeq+1), LAMBDA(a,b, a*b ) ),, cC-cSeq+1 )\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

LET 版本的高效公式

\n
=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 ) ) )\n
Run Code Online (Sandbox Code Playgroud)\n