【问题标题】:Generate All Permutations in Excel using LAMBDA使用 LAMBDA 在 Excel 中生成所有排列
【发布时间】:2022-03-13 19:58:53
【问题描述】:

这是一个常见问题解答:如何在 Excel 中生成所有排列

2011 2016 2017 2017 superuser 2018 2021

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

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

重述 2022 年问题

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

输入:

Country Variable Year
GB GDP 2015
DE area 2016
CH area 2015

输出:

Country Variable Year
GB GDP 2015
GB GDP 2016
GB area 2015
GB area 2016
DE GDP 2015
DE GDP 2016
DE area 2015
DE area 2016

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

扩展 2018 年问题

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

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

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

这可以用公式来完成吗?我找到了一个包含 2 列的公式,但我无法将其正确扩展到 3 列

两列公式:

=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
A F K P U 1
B G L Q V 2
C H R W 3
D X 4
E

这将产生 1'440 个排列。

L1 L2 L3 L4 L5 L6
A F K P U 1
A F K P U 2
A F K P U 3
A F K P U 4
A F K P V 1
A F K P V 2
A F K P V 3
A F K P V 4
A F K P W 1
... ... ... ... ... ...

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

【问题讨论】:

  • 难以置信...我在发布答案时出现蓝屏 - 仍在恢复中...
  • 顺便说一句,对于那些想要将 Table 输出转换为连接字符串表的人,您可以将下面答案中选择的公式包装在 =BYROW( yourChosenPermutationFormula, LAMBDA(x,TEXTJOIN("-",1,x))) 中,它将生成一个字符串表根据一些相关问题的要求,例如上面的2021 问题。

标签: excel excel-formula permutation excel-lambda


【解决方案1】:

很酷的问题和脑洞;我只是对使用 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))

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

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

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


选项 2:

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

A B C
a 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))) 对每一列进行排序,因此值在最前面,所有错误都被下推:
A B C
a d f
b e g
c e g
#N/A #N/A h
#N/A #N/A #N/A
  • 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 变量更改为更长的公式,以预先删除每列中的重复项。两种变体都可以正常工作。

【讨论】:

  • 这真是天才! MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1 是一件艺术品。您的版本集成了 strand f 而不是强制出错。但是,它效率不高,因为它仍然会为整个矩阵生成一个排列,所以 100X3 矩阵仍然会生成 1m 个元组。但是通过避免 strande f 问题,在功能上有很大的收获。我想到了一个想法:如果您将最终的FILTER 包装在UNIQUE 中,那么在用户在一列符号中多次使用相同符号的情况下,您将防止重复。出色的工作。
  • @markfitzpatrick。感谢您的客气话和编辑。我可以告诉您在其他范围内对其进行了测试,因为第一个变量现在已经跑调了。它适用于我认为的任何范围,从单个单元格、单行、单列到二维数组。此外,实际上,这将是UNIQUE() 中的一个简单包装。好主意。
  • 哎呀 - 对不起 - 你是对的。我忘了把它带回A1:C3。我屏住呼吸,我没有在重新格式化时损坏它。在 1m 元组上,它似乎确实生成了它们。如果将 A1:C100 作为输入矩阵 (A),则变量 E 生成 100^3 个元组。顺便说一句 - 我不仅测试了它,我还把它加载到了我的库中,cmets 归功于你,我现在将研究它一段时间 - MMULT 也是一件艺术品。
  • @markfitzpatrick,我添加了您使用UNIQUE() 的想法。有效地提出问题以找到所有 unique 排列。它没有在最终数组中使用,因为MMULT() 是这里唯一可以减慢速度的函数。因此,为什么它是在MAKEARRAY() 的结果上完成的。这使得FILTER()MMULT() 只能做最大要求的事情。我希望这次更新有所帮助。我试图解释我的数学逻辑,但很难用语言来理解 =)
  • @markfitzpatrick,这绝对会让我大吃一惊。在我做的地方使用UNIQUE() 更符合常识,因为它会限制需要在MMULT() 中处理的行,而将它放在过滤之后,它首先必须处理更多行并且仍然必须执行相同数量的在UNIQUE() 中处理。奇怪的结果,但是,嘿,excel 有时会以神秘的方式工作。
【解决方案2】:

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 魔术

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

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

我从JvdV's 得到这个答案到this question,专门用于解决this questionScott Craner & Bosco 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 ) ) )

【讨论】:

  • 我想知道是否有更简单的解决方案:处理 2 个范围的 Lambda,可以嵌套任意次数。称它为Perms,像=Perms(r1, r2) 一样使用它。像=Perms(Perms(r1, r2), r3) 一样嵌套,或者根据需要嵌套多个级别。输入范围可以是 Unique'd、Filter'd 和或 Sort'd 根据需要
  • @chrisneilsen Scott Craner 建议递归 LAMBDA 可能是要走的路。关于困 f 问题,我差一点就解决了,但我想知道 LAMBDA 的帮助者是否也能解决这个问题,所以我的见解不够用了。这样它就会少一种破坏的方式。
【解决方案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 背后的逻辑是迭代地(通过在MyLambda 中调用MyLambda(n+1))连接范围内的连续列,基于以下事实:两个正交数组的串联会生成一个二维数组,其中包含这两个数组元素的所有排列。

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

例如,生成

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

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

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

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

我们实际上是:

unpivoting:
  {"Bob","Steve","Paul";"Bob","Steve","Paul";"Bob","Steve","Paul"}
by:
  {"Dave";"Mike"}

所以输入数组n是:

A B
Dave Bob
Mike Steve
Paul

B 列被转置和复制:

A B C D
Dave Bob Steve Paul
Mike Bob Steve Paul

现在我们通过 A 列 UNPIVOT(一种扁平化)B、C 和 D 列:

A B
Dave Bob
Dave Steve
Dave Paul
Mike Bob
Mike Steve
Mike Paul

注意:这种方法:

  • 高效地生成所有排列而不重复
  • 忽略空格并且不需要符号位于相邻行上

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

【讨论】:

  • 嘿乔斯 - 我今晚要试试这个。进行递归方法是一个有趣的想法。您的两条规则与“高效”方法的规则相匹配。通常,我希望避免串联输出,因为串联可以应用于表输出,而反过来很难。
  • 嗨,马克。输出本身(由第二个函数调用)没有连接,只有LAMBDA
  • 所以,我不得不试了几次。这是一种创造性的方法。您是否认为有一种方法可以在不进入代码的情况下将目标范围作为参数传递? (顺便说一句 - 我虽然 MyLambda 很无聊,所以我在我的机器上将它命名为 WOOLLEFY。)
  • 嗨,马克。当然,不着急,只要有机会就添加它。将有兴趣看看您提出了哪些改进!干杯。
  • 别担心,马克。感谢更新。这周自己忙于工作,所以在不太远的某个时间赶上你。同意重新调试递归设置 - 噩梦!
【解决方案4】:

我的previous answer(s)有问题:

  • 简单公式会忽略空格,并且不需要符号位于同一列中的相邻行上,但效率低下。如果给定一个数组,A 列有 100 行,B 列有 3 行,C 列有 2 个,结果应该是100 x 3 x 2 = 600,但它会生成100 x 100 x 100 = 1'000'000,然后过滤它们。计算时间超过 2 分钟!如果输入数组中的任何列完全空白,它也会爆炸。
  • Efficient General LET 速度快且不会浪费计算量,但它无法处理非相邻行上的符号输入(搁浅的 f),并且还会崩溃如果任何一列完全空白,则向上。

Jos Woolley 解决方案的见解

Jos Woolleysolution 提供了两个见解,可以提供没有我之前的任何一种方法的问题的有效解决方案:

  • 目标实际上是分解每一列,并按下一列依次反透视
  • 这个想法可以通过LAMBDA递归来实现

递归 LAMBDA 解决方案

有了这些见解,我能够提出一个高效的 LAMBDA 解决方案。以下是完整的注释,以便您可以将其直接粘贴到您的高级公式编辑器中:

PERMUTATEARRAY = 
 /* This function recursively generates all unique permutations of an ordered array of symbols.
    It is efficient - i.e., it does not generate duplicate rows that require filtering.
    The arguments are:
        symbolArray - ia a required array input (unless the recursion is done).
            Is an array of ordered symbols where the left most column contains symbols that will be permutated
            by the permutation of the symbols in the next columns to the right. e.g.,
                with symbolArray of:
                    A   1
                    B   2
                A and B will be permutated by 1 and 2:
                    A   1
                    A   2
                    B   1
                    B   2
        byArray - optional array that will be used to permutate the next column of the symbolArray.
            This is passed between recursions and it not intended for use by the user but it can be used.
        cleaned - optional argument to indicate that the symbolArray has already been cleaned.
            This prevents the function from repeatedly cleaning the symbolArray that would otherwise require
            a repetition for each column of the symbolArray. It is passed between recursions and it not
            intended for use by the user but can be used.
    Example - With a symbolArray of:
        A   C   1
        B   D   2
        The output would be the following array:
        A   C   1
        A   C   2
        A   D   1
        A   D   2
        B   C   1
        B   C   2
        B   D   1
        B   D   2
    NOTES:
     -  Blanks will be ignored.
     -  errors will be ignored. (see comments below to change this)
     -  all rows of the resulting array will be unique
     -  blank columns in the symbol array are removed
     -  this function has no dependencies on external LAMBDA functions even though that would make it more
        readable.
 ------------------------------------  */

LAMBDA( symbolArray, [byArray], [cleaned],
    IF( AND(ISOMITTED(symbolArray),ISOMITTED(byArray)), ERROR.TYPE(7), // DONE
        IF(ISOMITTED(symbolArray), byArray, // if there is no symbolArray, the function is DONE.
            LET(                
                clnSymArray, IF(ISOMITTED(cleaned), //If the symbol array has not been cleaned, then clean it.
                /* Only clean arrays can be permuated. They cannot contain blanks because those are interpreted as 0's.
                   The input also cannot contain entirely blank columns, so these are removed.
                   The input cells also cannot contain errors as this will cause the whole function to error. That,
                   however, is a design choice. They are filtered out inside of this function because the user cannot
                   easily filter them out before passing them as arguments - IFERROR(x,"") causes all blanks to become 0's.
                */
                            LET(
                                COMPRESSC, LAMBDA( a, 
                                        FILTER(a,BYCOL(a,LAMBDA(a,SUM(--(a<>""))>0)))
                                        ),
                                REPLBLANKS, LAMBDA(array, [with], 
                                        LET(w, IF(ISOMITTED(with), "", with),
                                            IF(array = "", w, array)
                                            )
                                        ),
                                REPLERRORS, LAMBDA(array, [with], 
                                        LET(w, IF(ISOMITTED(with), "", with),
                                            IFERROR(array, w)
                                            )
                                        ),
                                COMPRESSC( REPLERRORS( REPLBLANKS(symbolArray) ) )
                                // COMPRESSC( REPLBLANKS(symbolArray) )  //removes the REPLERRORS if the user wants errors to result in erroring the function.
                                    ),
                            symbolArray ), //otherwise, pass the symbolArray
                
                //Once cleaned, effectively execute PERMUTATEARRAY( clnSymArray, byArray, 1 )
                IF( AND(COLUMNS( clnSymArray ) = 1, ISOMITTED( byArray ) ),
                    UNIQUE(FILTER(clnSymArray,clnSymArray<>"")), /* if the user gives a single column, give it back clean even if it was already cleaned.
                                                                    there is no point in testing again whether Clean has been set. DONE */
                    /* Otherwise, we can recursively process the inputs in the following LET. */
                    LET(
                        // MUX is an internal LAMBDA function that permutates the left most column of the p array by the b (by) array.
                            MUX, LAMBDA( p, b, 
                                    LET(pR, ROWS( p ),
                                        byR, ROWS( b ),
                                        byC, COLUMNS( b ),
                                        byCseq, SEQUENCE(,byC+1), // forces this to look at only one column of p
                                        oRSeq, SEQUENCE( byR * pR,,0 ),
                                        IFERROR( INDEX( b, oRSeq/pR+1, byCseq),
                                                INDEX( p, MOD(oRSeq,pR )+1, byCseq-byC ) )
                                        )
                                    ),
                            pRSeq, SEQUENCE(ROWS(clnSymArray)),
                        // Decide when to apply MUX versus when to recurse. MUX is always the final output.
                            // if there are only two symbol columns with no byArray, filter & MUX the two columns - DONE
                            IF( AND(COLUMNS( clnSymArray ) = 2, ISOMITTED( ByArray ) ),
                                LET(pFin, INDEX( clnSymArray, pRSeq, 2),
                                    fpFin, UNIQUE(FILTER(pFin,pFin<>"")),
                                    bFin, INDEX( clnSymArray, pRSeq, 1),
                                    fbFin, UNIQUE(FILTER(bFin,bFin<>"")),
                                    MUX( fpFin, fbFin )
                                    ),
                                // if there are more than two symbol columns with no byArray, repartition the symbol and byArray and recurse 
                                IF( AND(COLUMNS( clnSymArray ) > 2, ISOMITTED( ByArray ) ), 
                                    LET(pC, COLUMNS(clnSymArray),
                                        pCSeq, SEQUENCE(,pC-2,3),
                                        pNext, INDEX( clnSymArray, pRSeq, pCSeq ),
                                        pFin, INDEX( clnSymArray, pRSeq, 2),
                                        fpFin, UNIQUE(FILTER(pFin,pFin<>"")),
                                        bFin, INDEX( clnSymArray, pRSeq, 1),
                                        fbFin, UNIQUE(FILTER(bFin,bFin<>"")),
                                        bNext, MUX( fpFin, fbFin ),
                                        PERMUTATEARRAY( pNext, bNext, 1 )
                                        ) ,
                                    // if there is more than one symbol column and a byArray, repartition the symbol and byArray and recurse
                                    IF( AND(COLUMNS( clnSymArray ) > 1, NOT( ISOMITTED( ByArray ) ) ),
                                        LET(pC, COLUMNS(clnSymArray),
                                            pCSeq, SEQUENCE(,pC-1,2),
                                            pNext, INDEX( clnSymArray, pRSeq, pCSeq ),
                                            pFin, INDEX( clnSymArray, pRSeq, 1),
                                            fpFin, UNIQUE(FILTER(pFin,pFin<>"")),
                                            bNext, MUX( fpFin, ByArray ),
                                            PERMUTATEARRAY( pNext, bNext, 1 )
                                                ),
                                        // if there is only one symbol column and a byArray, filter symbol column & MUX it with he byArray - DONE
                                        IF( AND(COLUMNS( clnSymArray ) = 1, NOT( ISOMITTED( ByArray ) ) ),
                                            LET(pFin, INDEX( clnSymArray, pRSeq, 1),
                                                fpFin, UNIQUE(FILTER(pFin,pFin<>"")),
                                                MUX( fpFin, ByArray )
                                                )
                                )
                            ) ) )
                        ) ) )
            )
        )

);

即使你删除了 cmets,这里也有很多代码,但它是这样设计的,以便在防止错误的同时最大限度地提高速度。它也是一个完全包含的 LAMBDA,这意味着它不需要加载任何其他 LAMBDA 函数即可工作。

它能够处理来自同一列的非相邻行中的符号(搁浅的 f 问题)、完全空白的列以及输入数组中的错误。这是一个存在所有这些问题的示例:

LAMBDA 魔术 --> 图灵完备

在我之前的回答中,我可以看到 LAMBDA 允许我们现在有一个可扩展的解决方案,这要归功于使用 LAMBDA Helpers,正如 JvdV 所展示的那样,使用 SCAN(array,LAMBDA(a,b,a*b)) 运行排列。

现在,LAMBDA 允许递归和循环,对于这个特定问题更加强大。如果没有 Jos 的洞察力,我不会认识到存在重复递归模式。这个新的解决方案可能很长,但它以一种计算效率更高的方式解决了这个问题,从而防止电子表格变得不必要地滞后。

不利的一面是,调试递归是一个巨大的痛苦!

【讨论】:

  • 这是详尽的解释,值得一票,感谢您为得出结论所做的研究和努力。干得好,马克先生!
  • 干杯,@MayukhBhattacharya!
  • 看起来很棒,马克!当我有更多时间时会更详细地消化。
  • 干杯@JosWoolley!我也有同样的问题。很多真正的工作,这几乎是一种爱好。我怀疑公式可以收紧,但每次努力都很痛苦,因为调试递归很困难。但是,我做了速度测试,它比较快。它可以立即创建一个 600 个元素的 100 X 3 数组,并在大约 3 秒内完成一个 1m 元素、100 X 3 的数组,而 Simple 需要 35 秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-14
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多