这是一个经常被问到和回答的问题:如何在Excel中生成所有排列:
201120162017 2017超级用户2018 2021
现在到了2022年,在它作为重复被关闭之前没有得到答案,这很不幸,因为LAMBDA真的改变了这个问题的答案。
我很少有同样的需求,并因不得不重新发明一个复杂的轮子而感到沮丧。所以,我会重新提出这个问题,并在下面给出我自己的答案。我不会将任何意见书标记为答案,但邀请好的想法。我相信我自己的方法可以改进。
重申2022年问题
我试图在Excel中创建一个仅包含公式的循环。我试图实现的目标如下所述。假设我有3列作为输入:㈠国家;(ii)可变的;及(iii)年。我想从这些输入展开,然后为这些参数赋值。
输入:
| 国家|变量|年份|
| - -----|- -----|- -----|
| GB|国内生产总值|2015年|
| DE|面积|2016年|
| CH|面积|2015年|
输出:
| 国家|变量|年份|
| - -----|- -----|- -----|
| GB|国内生产总值|2015年|
| GB|国内生产总值|2016年|
| GB|面积|2015年|
| GB|面积|2016年|
| DE|国内生产总值|2015年|
| DE|国内生产总值|2016年|
| DE|面积|2015年|
| DE|面积|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级|K|压力|U|一个|
| B| G级|L形|Q类|V| 2|
| C类|H型||R型|W| 3|
| D||||X| 4|
| E级||||||
这将产生1 '440个排列。
| L1| L2| L3| L4| L5| L6|
| - -----|- -----|- -----|- -----|- -----|- -----|
| 一个|F级|K|压力|U|一个|
| 一个|F级|K|压力|U| 2|
| 一个|F级|K|压力|U| 3|
| 一个|F级|K|压力|U| 4|
| 一个|F级|K|压力|V|一个|
| 一个|F级|K|压力|V| 2|
| 一个|F级|K|压力|V| 3|
| 一个|F级|K|压力|V| 4|
| 一个|F级|K|压力|W|一个|
| ......这是什么?|......这是什么?|......这是什么?|......这是什么?|......这是什么?|......这是什么?|
**制作一个包含任意数量的水平(列)的通用公式是很难的。只要浏览一下提供的答案-它们每个都需要一些火箭科学,直到现在,所有的解决方案都对符号的列数进行了硬编码限制。那么LAMBDA能给予我们一个通用的解决方案吗?
5条答案
按热度按时间vdzxcuhz1#
冷静的问题和脑筋急转弯;我只是对使用
MAKEARRAY()
的东西感到困惑:选项一:
你所谓的“超级低效”是在计算行^列时创建一个排列列表。我认为下面的方法并不那么低效。让我们想象一下:
E1
中的公式:简而言之,它的作用是:
INDEX()
s来返回所有值。为此,我们需要为行和列都设置正确的索引。MAKEARRAY()
将使计算相对容易,因为lambda带来了递归功能。在这些函数中,它的基本数学运算为这些行和列返回正确的索引。事实上,不需要对列进行计算,因为我们简单地引用'cl',并且所有行索引的所有计算都通过MOD(CEILING(rw/(D/(B^cl)),1)-1,B)+1
完成。UNIQUE()
中,以使用非常少的资源来过滤掉任何潜在的重复并将潜在的空行限制为仅一个空行。FILTER()
和MMULT()
可以很好地协同工作,过滤掉任何不需要的结果(读取;空)。这是我能做的最紧凑最快的了。公式现在将适用于任何连续的单元格区域。单个单元格、单行、单列或任何二维范围。
选项二:
OP正确地提到,选项1可能会在开始时创建太多的元组,以至于后来才丢弃它们。这可能是低效的。为了解决这个问题(如果这不是你想要的),我们可以使用一个更大的公式。让我们想象以下数据:
| 一个|B| C类|
| - -----|- -----|- -----|
| a| d| f|
| B| e的|h的|
| | e的||
| c型||g|
| | | g|
我们看到有空单元格和重复的值。这就是选项1会创建太多元组的原因。为了解决这个问题,我提出了一个更长的公式:
要对此进行分解:
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)))
对每列进行排序,因此值位于顶部,所有错误都被向下推:| 一个|B| C类|
| - -----|- -----|- -----|
| a| 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
变量中当前column-indice之后的所有列。这是一个相当大的块消化,不幸的是,我不足以解释=)。UNIQUE(G)
-最后一步是过滤掉所有的双重排列,如果你也选择的话。结果:
现在,即使选项1在可读性方面优于选项2,但第二个选项(在非常有限的测试中)只花了第一个选项的三分之一的时间来计算。所以从速度上来说,第二种选择是首选。
作为第二个选项的替代方案,我首先有:
这将把
D
变量改为一个更长的公式,以预先删除每列中的重复项。这两种变化都能很好地工作。xmq68pz92#
LAMBDA支持的Simple LET
对于2022年的问题,我使用了以下LET:
其中用于在A2:E6:
此公式插入“°|“°”代替空白,以避免将空白重铸为0。它通过应用UNIQUE来消除重复项。
这是***超级低效***,因为它生成每一个可能的排列,包括重复,然后过滤掉它们。对于一个小规模的矩阵,这不是什么大问题,但是想象一下,一个100行乘3列的矩阵将生成1'000'000行,然后将它们过滤到一个小的子集。
但是,有一个小的好处,*注意黄色单元格中的 f,它被困在列的中间,与它的对等单元格不相邻。这个公式处理得很好。它只是将其集成到有效排列的输出中。这在下面的 * 有效 * 公式中很重要。
LAMBDA支持的高效通用LET
对于一般的LAMBDA公式,我使用:
这需要矩阵,就像上面的 (LET版本如下所示)。它提供了完全相同的结果,但有显着的差异:
5^6 = 15625
排列,以得到1440
有效排列。这仅生成所需的内容,而不需要过滤。因此,存在错误检测和处理。变量er使用LAMBDA Helper检测矩阵中是否有任何与其上方的符号不按列连续的搁浅符号:
这是一个新的挑战,也可能是一个新的问题。在搁浅的***f***的情况下,有人能想出一种方法来合并它吗?
LAMBDA Magic
LAMBDA带来的魔力来自这一行,它使两个公式都可以扩展到任何数量的列,而不必对它们进行硬编码:
我从JvdV's的答案中得到了这个答案,它专门针对试图解决这个问题。Scott Craner和Bosco Yip已经表明,如果没有LAMBDA,它基本上是无法解决的,JvdV展示了如何使用LAMBDA助手
SCAN
来完成。有效公式的LET版本
7uhlpewt3#
可能是 iterative
LAMBDA
:MyLambda在名称管理器中定义为:
之后我们就可以调用
=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"}&TRANSPOSE({"Bob";"Steve";"Paul"})
例如,生成
{"DaveBob","DaveSteve","DavePaul";"MikeBob","MikeSteve","MikePaul"}
然后需要将其“重定尺寸”为一维水平阵列,即,
{"DaveBob";"DaveSteve";"DavePaul";"MikeBob";"MikeSteve";"MikePaul"}
使得它可以与该范围内的下一个(垂直的、正交的)列迭代地连接。等等。
我们有效地:
因此,输入数组 n 是:
| 一个|B|
| - -----|- -----|
| 戴夫|鲍勃|
| 迈克|史蒂夫|
| | 保罗|
列B被转置和复制:
| 一个|B| C类|D|
| - -----|- -----|- -----|- -----|
| 戴夫|鲍勃|史蒂夫|保罗|
| 迈克|鲍勃|史蒂夫|保罗|
现在我们通过列A对列B、C和D进行UNPIVOT(一种展平):
| 一个|B|
| - -----|- -----|
| 戴夫|鲍勃|
| 戴夫|史蒂夫|
| 戴夫|保罗|
| 迈克|鲍勃|
| 迈克|史蒂夫|
| 迈克|保罗|
注:此方法:
第二个函数在工作表中调用,然后将串联的数组解析为所需的行数/列数。
lb3vh1jj4#
我以前的回答有问题(S):
Simple公式忽略空格,并且不要求符号位于同一列中的相邻行,但效率低下。如果给定一个数组,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的解决方案提供了两个见解,允许一个有效的解决方案,不存在我以前的任何一种方法的问题:
递归LAMBDA解决方案
有了这些见解,我能够提出一个有效的LAMBDA解决方案。以下内容已完全注解,以便您可以将其直接粘贴到高级公式编辑器中:
即使您删除了注解,这里也有很多代码,但这样设计是为了在防止错误的同时最大限度地提高速度。它也是一个完全包含的LAMBDA,这意味着它不需要加载任何其他LAMBDA函数即可工作。
它能够处理来自同一列的非相邻行中的符号 (搁浅的f问题),完全空白的列和输入数组中的错误。下面是一个具有所有这些问题的示例:
LAMBDA Magic --> Turing Complete
在我之前的回答中,我可以看到LAMBDA允许我们现在有一个可扩展的解决方案来解决这个问题,这要归功于LAMBDA助手的使用,就像JvdV使用
SCAN(array,LAMBDA(a,b,a*b))
运行排列所展示的那样。现在,LAMBDA允许递归和循环,这对于这个特定的问题来说甚至更强大。如果没有Jos的洞察力,我就不会认识到有一个重复的递归模式。这个新的解决方案可能很长,但它以一种计算效率更高的方式解决了这个问题,这将防止电子表格变得不必要的滞后。
缺点是,调试递归是一个巨大的痛苦!
w1e3prcc5#
这个方法怎么样:
它从第一列中的(唯一)值开始,将这些值中的每个值与第二列中的每个唯一值连接起来。结果被存储,并且该存储的值中的每一个与下一列的每个唯一值联接,等等。最后,这是textsplitted得到逗号分隔的单元格值,以分隔列再次。