使用Excel查询进行组合

kuhbmx9i  于 2023-03-13  发布在  其他
关注(0)|答案(5)|浏览(129)

我需要使所有可能的组合(而不是排列)与三个数字使用Excel查询。
我有六个数字在一列。从A1:A6。我需要使用Excel查询三个数字的所有可能的组合(而不是排列)。请帮助..

rqqzpn5f

rqqzpn5f1#

如果您所说的“excel查询”是指Power Query,那么下面是一个脚本:

算法

  • 读入原始表
  • 添加两个自定义列,其中每个单元格包含完整的原始表
  • 展开两个自定义列
  • 对三列表格的每一行进行排序
  • 删除重复项

=〉56种组合(允许同一组合中的数字重复)

  • 请注意,下面的代码可以从Power Query用户界面生成 *
let

//Read in table and set data type
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"List", Int64.Type}}),

//Add two more columns where the rows of each column = the full original table
    #"Added Custom" = Table.AddColumn(#"Changed Type", "Custom", each #"Changed Type"),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Custom.1", each #"Changed Type"),

//Expand each of the two custom columns
    #"Expanded Custom" = Table.ExpandTableColumn(#"Added Custom1", "Custom", {"List"}, {"Custom.List"}),
    #"Expanded Custom.1" = Table.ExpandTableColumn(#"Expanded Custom", "Custom.1", {"List"}, {"Custom.1.List"}),

//Add a column which contains a Sorted List of the three data columns
    #"Added Custom2" = Table.AddColumn(#"Expanded Custom.1", "Sorted Row", each List.Sort({[List],[Custom.List],[Custom.1.List]})),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom2",{"List", "Custom.List", "Custom.1.List"}),

//Remove the duplicates in the list => 56 items
    #"Removed Duplicates" = Table.Distinct(#"Removed Columns"),

//expand the list into a delimited list
    #"Added Custom3" = Table.AddColumn(#"Removed Duplicates", "Custom", 
        each Text.Combine(List.Transform([Sorted Row],each Text.From(_)),"~"), type text),
    #"Removed Columns1" = Table.RemoveColumns(#"Added Custom3",{"Sorted Row"}),

//split the column by the delimiter
    #"Split Column by Delimiter" = Table.SplitColumn(#"Removed Columns1", "Custom", 
        Splitter.SplitTextByDelimiter("~", QuoteStyle.Csv), {"Custom.1", "Custom.2", "Custom.3"}),
    #"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{
        {"Custom.1", Int64.Type}, {"Custom.2", Int64.Type}, {"Custom.3", Int64.Type}})
in
    #"Changed Type1"

原始数据

重复值组合

编辑以泛化代码

  • 请注意,此代码对于大型集合效率较低,因为它首先生成所有排列,然后通过对行进行排序并删除重复项来消除无效值。*
  • 对于从一组8个项目中选择7个项目,排列数超过2,000,000,Excel Power Query无法有效处理,因此需要一段时间才能运行。*
  • 在Power BI Desktop上运行的相同代码只需一两秒 *

我将其编写为名为fnCombos的自定义函数

自定义函数

  • 重命名fnCombos*
(l as list, num as number)=>

let
  t = Table.FromColumns({l}),

  #"Permutations" = List.Last(
    List.Generate(
      ()=>[T=t, idx=0],
      each [idx] < num,
      each [T = Table.AddColumn([T],Text.From([idx]), each l), idx=[idx]+1],
      each [T])),

  #"Expand Columns" = List.Accumulate({"0"..Text.From(num-2)},#"Permutations", (state, current)=>
      Table.ExpandListColumn(state,current)),

  //Sort each row
  //  Then remove duplicates
  #"Sort Rows and DeDupe" = List.Distinct(Table.TransformRows(#"Expand Columns", (r) => 
      Record.FromList(List.Sort(Record.FieldValues(r)),Record.FieldNames(r)))),

    #"Converted to Table" = Table.FromList(#"Sort Rows and DeDupe", Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", Table.ColumnNames(#"Expand Columns"))
  
in
    #"Expanded Column1"

主查询

let

//Read in table and set data type
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"List", Int64.Type}}),

//Call custom function to create the combinations
    #"Combos" = fnCombos(#"Changed Type"[List],7)

in #"Combos"
qybjjes1

qybjjes12#

要获取56行:

=LET(list,A2:B7,n,ROWS(list),draw,B2,nTot,n^draw,all,SEQUENCE(nTot,1,0),pick,BASE(all,n,draw),allComb,MAKEARRAY(nTot,draw,LAMBDA(r,c,INDEX(list,MID(INDEX(pick,r),c,1)+1))),UNIQUE(DROP(REDUCE("",all,LAMBDA(a,c,VSTACK(a,SORT(INDEX(allComb,c+1,0),,,TRUE)))),1)))

改进公式:

=LET(list,DROP(TOCOL(A:A,1),1),
n,ROWS(list),
draw,B2,
nTot,n^draw,
all,SEQUENCE(nTot,1,0),
allComb,MAKEARRAY(nTot,draw,LAMBDA(r,c,INDEX(list,MOD(QUOTIENT(INDEX(all,r),n^(draw-c)),n)+1))),
UNIQUE(DROP(REDUCE("",all,LAMBDA(a,c,VSTACK(a,SORT(INDEX(allComb,c+1,0),,,TRUE)))),1)))

  • 注 *

OP是正确的,应该有56行。您可以检查此here,其中使用的公式为

或在Excel中使用

=COMBINA(6,3)
ego6inou

ego6inou3#

备选方案:

=LET(
    ζ,A2:A7,
    ξ,REPT(" ",9),
    κ,0+TRIM(MID(TOCOL(TOROW(ζ&ξ&TOROW(ζ))&ξ&ζ),9*{0,1,2}+1,9)),
    UNIQUE(MAKEARRAY(ROWS(κ),3,LAMBDA(α,β,SMALL(INDEX(κ,α,),β))))
)

这当前对于3个号码的所有组合是静态设置的,但是如果需要也可以动态设置。
范围内的较大数字可能需要REPT的较大第二个参数。

omjgkv6w

omjgkv6w4#

修改了我的答案,以允许组合而不是排列。因此太冗长,但我会离开它。
要用公式写出这一点,可以用途:

C2中的公式:

=LET(x,A2:A7,y,DROP(REDUCE(0,x,LAMBDA(a,b,VSTACK(a,IFERROR(HSTACK(b,DROP(REDUCE(0,x,LAMBDA(c,d,VSTACK(c,IFERROR(HSTACK(d,x),d)))),1)),b)))),1),UNIQUE(DROP(REDUCE(0,SEQUENCE(ROWS(y)),LAMBDA(a,b,VSTACK(a,SORT(INDEX(y,b),,,1)))),1)))
n8ghc7c1

n8ghc7c15#

这里,使用Excel函数的另一种替代方法。我们使用 * 递归函数 * 生成输入数据的所有索引位置。这种方法 * 精确地生成我们需要的组合集,而无需生成额外的组合集,然后需要删除无效的值集,如重复值等,因此计算步骤*得到优化
您需要在 Name Manager(如果您不想使用公式,请参见末尾的公式)中输入以下用户LAMBDA函数:NEXT_ROW

=LAMBDA(x,m,n,i, IF(i=0, x, LET(s, SEQUENCE(,n), idx, XMATCH(m, IF(i<n, 
 IF(s>i, m+1, x+1), x+1),-1,-1), if(idx=i, x + N(s=i), 
 NEXT_ROW(IF(s=i, INDEX(x+1,idx), x),m,n,i-1)))))

**UPDATE:**该功能可简化如下:

=LAMBDA(x,m,n,i, IF(i=0, x, LET(s, SEQUENCE(,n), y, x+1, idx, XMATCH(m, IF(i<n, 
 IF(s>i,m+1,y),y),-1,-1),NEXT_ROW(IF(s=i,INDEX(y,idx),x),m,n,IF(idx=i,0,i-1)))))

其中:

  • x,索引位置形状为的数组:1 x n .
  • m,需要分布在n位置的输入值的总数。
  • n,要分配的索引位置总数
  • i,要计算的第一个索引位置。在每次迭代中,我们将索引位置减一。当i=0时,我们完成循环,返回下一行的数组。我们从右到左开始,所以i=n

NEXT_ROW,根据输入数组x返回下一行的索引位置。例如,第一个元素在以下范围内:A1:C1表示问题中的样本数据,其中m=6n=3,则:
这与我们需要的索引位置相对应。

注意:如果您使用的是Excel Web,它不提供对 * 名称管理器 * 的访问,您可以安装以下加载项:Advanced Formula Environment。下面是NEXT_ROW函数的视图:

有了所有可能的索引位置的序列,生成最终结果是简单的:

=LET(in, A2:A7, m, ROWS(in), n,3, cnts, SEQUENCE(COMBINA(m,n)-1),
 idx, REDUCE(SEQUENCE(,n,1,0), cnts, LAMBDA(ac,i,
 VSTACK(ac, NEXT_ROW(IF(i=1, ac, TAKE(ac,-1)),m,n,n)))),INDEX(in, idx))

我们使用REDUCE/VSTACK模式来生成索引位置的整个集合。如何将Excel中表格从垂直转换为水平但长度不同。
我们用第一组索引位置初始化REDUCE的累加器,这是一个1的常数数组:SEQUENCE(,n,1,0),这就是为什么我们需要从所有总组合中少一次迭代:COMBINA(m,n),带替换件。
下面是输出:

您可以将整个过程封装在新用户LAMBDA函数COMBINA_SET中,并添加到 Name Manager 中,以便将来重用:

=LAMBDA(x, n, LET(y, TOCOL(x), m, ROWS(y), IF(AND(n=1,m=1), x,
 LET(cnts, SEQUENCE(COMBINA(m,n)-1), idx, REDUCE(SEQUENCE(,n,1,0), cnts,
 LAMBDA(ac,i, VSTACK(ac, NEXT_ROW(IF(i=1, ac, TAKE(ac,-1)),m,n,n)))),
 INDEX(y, idx)))))

然后,您可以按如下方式调用它:

COMBINA_SET(A2:A7,3)

我们考虑更一般情况下的其他情景:
1.处理n=1m=1的特殊情况,我们不需要递归过程,对于这种情况,前面的公式产生了错误。
1.允许一般情况,其中输入参数x可以是列数组。

注意:无论如何你都需要创建NEXT_ROW,因为你不能在LET语句中创建递归函数。你可以 * 克服它 *,遵循这篇文章的建议:LAMBDA Formulaic Recursion: It’s All About ME!(感谢@JosWoolley分享此链接):

=LAMBDA(x, n, LET(NEXT_SET, LAMBDA(ME,x,m,n,i, IF(i=0, x, LET(s, SEQUENCE(,n), 
 y, x+1, idx, XMATCH(m, IF(i<n, IF(s>i,m+1,y),y),-1,-1),ME(ME, IF(s=i,
  INDEX(y,idx),x),m,n,IF(idx=i,0,i-1))))), y, TOCOL(x), m, ROWS(y), 
 IF(AND(n=1,m=1), x, LET(cnts, SEQUENCE(COMBINA(m,n)-1), 
 idx, REDUCE(SEQUENCE(,n,1,0), cnts, LAMBDA(ac,i, VSTACK(ac, 
 NEXT_SET(NEXT_SET, IF(i=1, ac, TAKE(ac,-1)),m,n,n)))),INDEX(y, idx)))))

如果您确实不想使用 Name Manager,而前面的方法使用ME解决方案来绕过它,那么您可以在一个公式中包含所有内容:

=LET(A, A2:A7,n,3, COMBINA_SET, LAMBDA(x, n, LET(NEXT_SET, LAMBDA(ME,x,m,n,i, 
 IF(i=0, x, LET(s, SEQUENCE(,n), y, x+1, 
 idx, XMATCH(m, IF(i<n, IF(s>i,m+1,y),y),-1,-1),ME(ME, IF(s=i,
   INDEX(y,idx),x),m,n,IF(idx=i,0,i-1))))), y, TOCOL(x), m, ROWS(y), 
 IF(AND(n=1,m=1), x, LET(cnts, SEQUENCE(COMBINA(m,n)-1), 
  idx, REDUCE(SEQUENCE(,n,1,0), cnts, LAMBDA(ac,i, VSTACK(ac, 
  NEXT_SET(NEXT_SET, IF(i=1, ac, TAKE(ac,-1)),m,n,n)))),INDEX(y, idx))))),
 COMBINA_SET(A,n))

相关问题