我的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 Woolley 的 solution 提供了两个见解,可以提供没有我之前的任何一种方法的问题的有效解决方案:
- 目标实际上是分解每一列,并按下一列依次反透视
- 这个想法可以通过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 的洞察力,我不会认识到存在重复递归模式。这个新的解决方案可能很长,但它以一种计算效率更高的方式解决了这个问题,从而防止电子表格变得不必要地滞后。
不利的一面是,调试递归是一个巨大的痛苦!