【问题标题】:Iterating through combinations of groups of 4 within a group of 16在一组 16 中迭代 4 组的组合
【发布时间】:2019-01-13 15:09:41
【问题描述】:

大家好,我知道这个问题看起来与其他一些问题相似,但我已经广泛搜索它们,无法让它们为我工作。

我有 16 个数据集,我们称它们为 1 到 16。我想遍历所有可能的不同方法,将这 16 个数据集分为 4 个组;最基本的例子是:[1,2,3,4][5,6,7,8][9,10,11,12][13,14,15,16]。

问题是如何最好地遍历这些组合(在 vba 中)?

下面我提供了一个更详细的示例来帮助说明我正在努力实现的目标、我迄今为止的思考过程、我尝试过的代码以及它为什么不起作用。


示例另一个有效的组合可能是 [2,4,6,8][10,12,14,16][1,3,5,7][9,11,13, 15]等等。但是,我想避免任何重复:一种类型的重复将包括在一个组内重复的元素,或者相同组合的另一组:[1,2,2,4]... OR [1,2,3,4][4,5,6,7]... 2 类重复将涉及与前一次迭代相同的组,例如 [1,2,4, 3][5,6,8,7][9,10,12,11][13,14,16,15]。

思考过程我想避免任何重复,特别是因为这将大大减少我必须比较的组合数量。我试图通过使用比较组合中的所有元素以查看是否相同的函数来避免类型 1。我试图通过确保每个组中的元素始终按升序排列来避免类型 2,并确保每个组中的第一个元素也始终按升序排列。 (这应该有效,不是吗?)

代码 以下是我尝试过的两个代码示例。第一个简单地使 excel 崩溃(我确实有一个值,而不是 large number 如果这是你的想法);我想有太多的组合要一一经历? 第二个没有给我唯一的组,它返回相同的组,每个组中只有第一个值发生了变化。

1.

Sub CombGen()

Dim Combs(1 To 1820)
Dim Comb(1 To 4)


Dim GroupsCombs(1 To *large number*)
Dim GroupsComb(1 To 1820)



x = 1
For a = 1 To 16 - 3
Comb(1) = a
 For b = a + 1 To 16 - 2
 Comb(2) = b
  For c = b + 1 To 16 - 1
  Comb(3) = c
   For d = c + 1 To 16
    Comb(4) = d
    Combs(x) = Comb
    x = x + 1
   Next d
  Next c
 Next b
Next a


x = 1
For a = 1 To 1820 - 3
GroupsComb(1) = a
 For b = a + 1 To 1820 - 2
 GroupsComb(2) = b
  For c = b + 1 To 1820 - 1
  GroupsComb(3) = c
   For d = c + 1 To 1820
    GroupsComb(4) = d
    If Repeat(a, b, c, d, Combs) = False Then
     GroupsCombs(x) = Comb
     x = x + 1
    End If
   Next d
  Next c
 Next b
Next a


End Sub

Function Repeat(a, b, c, d, Combs)
 Repeat = False
 Dim letters(1 To 4): letters(1) = a: letters(2) = b: letters(3) = c: letters(4) = d
 Dim i: Dim j
 Repeat = False
 For x = 1 To 4
  For y = 2 To 4
   For i = 1 To 4
    For j = 1 To 4
     If Combs(letters(i))(x) = Combs(letters(j))(y) Then
      Repeat = True
     End If
    Next j
   Next i
  Next y
 Next x
End Function

2.

For a = 1 To 16 - 3
 For b = a + 1 To 16 - 2
  For c = b + 1 To 16 - 1
   For d = c + 1 To 16
    TempGroups(1, 1) = a: TempGroups(1, 2) = b: TempGroups(1, 3) = c: TempGroups(1, 4) = d

    For e = 1 To 16 - 3
    If InArray(TempGroups, e) = False Then
     For f = e + 1 To 16 - 2
     If InArray(TempGroups, f) = False Then
      For g = f + 1 To 16 - 1
      If InArray(TempGroups, g) = False Then
       For h = g + 1 To 16          
        If InArray(TempGroups, h) = False Then
        TempGroups(2, 1) = e: TempGroups(2, 2) = f: TempGroups(2, 3) = g: TempGroups(2, 4) = h

        For i = 1 To 16 - 3
        If InArray(TempGroups, i) = False Then
         For j = i + 1 To 16 - 2
         If InArray(TempGroups, j) = False Then
          For k = j + 1 To 16 - 1
          If InArray(TempGroups, k) = False Then
           For l = k + 1 To 16               
            If InArray(TempGroups, l) = False Then
            TempGroups(3, 1) = i: TempGroups(3, 2) = j: TempGroups(3, 3) = k: TempGroups(3, 4) = l

            For m = 1 To 16 - 3
            If InArray(TempGroups, m) = False Then
             For n = m + 1 To 16 - 2
             If InArray(TempGroups, n) = False Then
              For o = n + 1 To 16 - 1
              If InArray(TempGroups, o) = False Then
               For p = o + 1 To 16
               If InArray(TempGroups, p) = False Then
                TempGroups(3, 1) = m: TempGroups(3, 2) = n: TempGroups(3, 3) = o: TempGroups(3, 4) = p

                If *comparison criteria are met* Then
                 For x = 1 To 4
                  For y = 1 To 4
                   Groups(x, y) = TempGroups(x, y)
                  Next y
                 Next x
                End If

               End If
               Next p
              End If
              Next o
             End If
             Next n
            End If
            Next m

           End If
           Next l
          End If
          Next k
         End If
         Next j
        End If
        Next i

       End If
       Next h
      End If
      Next g
     End If
     Next f
    End If
    Next e

   Next d
  Next c
 Next b
Next a

End If

Groups 和 TempGroups 是二维数组,第一个值是组编号,第二个值是该组中的元素编号。
InArray 是我制作的一个函数(相当不言自明)
在这种情况下,我使用比较标准将最近的“最佳”组集与“临时组”的当前迭代进行比较,并保存最佳组,以便与下一次迭代进行比较

没有帮助的链接:
How can I iterate throught every possible combination of n playing cards 虽然这很有用,但它只查看了集合中一个组的组合,我想查看集合中多个组的组合

Listing all permutations of a given set of values 这更多地着眼于排列(重新排列组的顺序而不是组合)

我查看的几乎所有其他解决方案都属于这些类别之一

【问题讨论】:

  • 如果您链接到其他解决方案并说明它们为什么不适合您,您的问题将会得到改善。
  • this answer 不提供见解或指导吗?任何方式你做到这一点,排列的数量将是非常大的。
  • @PeterT 感谢您的回复。不幸的是,您链接的答案集中在排列和 Steinhaus 算法上;这只会交换我组中列出的每个元素的顺序,即从 [1234] 到 [1243] - 正如我所描述的那样,这是一种类型 2 重复。我不想要对我的组进行排序的每一个排列,我只想要每一个独特的组合来比较它们。
  • @JamesJenkins 我在底部添加了几个链接,包括彼得建议的链接,以及我遇到的问题
  • 我想我理解类型 1(不太确定),我对你对类型 2 组合的定义肯定有点困惑。你能给出这两种类型的更广泛的例子吗?我想我有一个优雅的解决方案来解决您的问题。

标签: excel vba iteration combinations permutation


【解决方案1】:

从概念上讲,这个问题并不难。我们需要做的就是生成所有16! 排列,并删除所有4 个组的组内重复4!。最后,我们需要删除整个组的重复次数 4!。所以我们应该得到近 300 万个结果:

16! / (4!^5) = 2,627,625

例如,如果我们考虑 lexicographical order 中从 1 到 16 的前 10 个排列,我们有:

 1 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 14 15 16)
 2 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 14 16 15)
 3 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 15 14 16)
 4 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 15 16 14)
 5 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 16 14 15)
 6 (1 2 3 4) (5 6 7 8) (9 10 11 12) (13 16 15 14)
 7 (1 2 3 4) (5 6 7 8) (9 10 11 12) (14 13 15 16)
 8 (1 2 3 4) (5 6 7 8) (9 10 11 12) (14 13 16 15)
 9 (1 2 3 4) (5 6 7 8) (9 10 11 12) (14 15 13 16)
10 (1 2 3 4) (5 6 7 8) (9 10 11 12) (14 15 16 13)

如您所见,所有这些都是相同的,因为最后一组是唯一被置换的东西(OP 不想要)。如果我们继续生成并查看排列 20 到 30,我们有:

20 (1 2 3 4) (5 6 7 8) (9 10 11 12) (16 13 15 14)
21 (1 2 3 4) (5 6 7 8) (9 10 11 12) (16 14 13 15)
22 (1 2 3 4) (5 6 7 8) (9 10 11 12) (16 14 15 13)
23 (1 2 3 4) (5 6 7 8) (9 10 11 12) (16 15 13 14)
24 (1 2 3 4) (5 6 7 8) (9 10 11 12) (16 15 14 13)
25 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 14 15 16) <- a different combination
26 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 14 16 15)
27 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 15 14 16)
28 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 15 16 14)
29 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 16 14 15)
30 (1 2 3 4) (5 6 7 8) (9 10 11 13) (12 16 15 14)

最后在排列 #25 处,我们得到了 OP 所追求的新的自定义组合。

如果我们继续下去,最终排列 #5606234726401(是的,超过 5 万亿)是一个示例,其中组与前几个排列完全相同,只有这些组被排列(再次,这些是排列我们想避免):

5606234726401 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 14 15 16) <- same as the 1st permutation
5606234726402 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 14 16 15)
5606234726403 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 15 14 16)
5606234726404 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 15 16 14)
5606234726405 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 16 14 15)
5606234726406 (5 6 7 8) (1 2 3 4) (9 10 11 12) (13 16 15 14)
5606234726407 (5 6 7 8) (1 2 3 4) (9 10 11 12) (14 13 15 16)
5606234726408 (5 6 7 8) (1 2 3 4) (9 10 11 12) (14 13 16 15)
5606234726409 (5 6 7 8) (1 2 3 4) (9 10 11 12) (14 15 13 16)
5606234726410 (5 6 7 8) (1 2 3 4) (9 10 11 12) (14 15 16 13)

关键是,我们需要一种方法来避免这些组内和组排列,因为生成和筛选这么多排列所需的绝对计算能力(无论算法有多高效)根本不可行.

我们需要一种不同的方法。让我们看一组 16 选择 4 的组合,比如 450 到 460:

450 (1 12 14 16)
451 (1 12 15 16)
452 (1 13 14 15)
453 (1 13 14 16)
454 (1 13 15 16)
455 (1 14 15 16)
456 (2 3 4 5)  
457 (2 3 4 6)  
458 (2 3 4 7)  
459 (2 3 4 8)  
460 (2 3 4 9)

我们在这里注意到,如果我们用前 455 个组合中不存在的组合填充其他 3 组,我们最终将复制组合 456 到 459。例如,组合 291 到 294 是:

291 (1 6 7 8) 
292 (1 6 7 9) 
293 (1 6 7 10)
294 (1 6 7 11)

如果我们要填写这些组合的补码的所有可能组合,请选择 4(例如,(2 3 4 5 9 10 11 12 13 14 15 16) 表示 291 的补码),那么前面显示的那些组合(456 到 459)将已经被计算在内为。

这是一个不错的结果。这意味着我们可以在第一个“组”完成后停止生成结果(例如,当第一组中的第一个数字保持 1 时)。当我们转移到更多的组时,同样的想法也适用。

下面我们有一些辅助函数,用于计算组合、生成组合和获取向量的补码。组合生成器非常高效,可以在我的旧 Windows 机器上在 3 秒多的时间内生成 25 选择 12 的所有 5,200,300 种组合。

Option Explicit

Function nCr(n As Long, r As Long) As Long
Dim res As Long, i As Long, temp As Double
    temp = 1
    For i = 1 To r: temp = temp * (n - r + i) / i: Next i
    nCr = Round(temp)
End Function

Sub GetCombosNoRep(ByRef combos() As Long, n As Long, r As Long, numRows As Long)

Dim index() As Long
Dim numIter As Long, i As Long, k As Long, count As Long

    ReDim index(1 To r)
    count = 1
    For i = 1 To r: index(i) = i: Next

    While count <= numRows
        numIter = n - index(r) + 1

        For i = 1 To numIter
            For k = 1 To r
                combos(count, k) = index(k)
            Next k
            count = count + 1
            index(r) = index(r) + 1
        Next i

        For i = r - 1 To 1 Step -1
            If index(i) <> (n - r + i) Then
                index(i) = index(i) + 1
                For k = i + 1 To r
                    index(k) = index(k - 1) + 1
                Next k

                Exit For
            End If
        Next i
    Wend

End Sub

Sub GetComplement(n As Long, childVec() As Long, complementVec() As Long)

Dim i As Long, j As Long

    ReDim logicalVec(1 To n)
    For i = 1 To n: logicalVec(i) = True: Next i
    For i = 1 To UBound(childVec): logicalVec(childVec(i)) = False: Next i
    j = 1

    For i = 1 To n
        If logicalVec(i) Then
            complementVec(j) = i
            j = j + 1
        End If
    Next i

End Sub

这是主要的子程序:

Sub MasterGenerator()

Dim myRows As Long, i As Long, j As Long, r As Long, n As Long
Dim combos() As Long, k As Long, gSize As Long, total As Long
Dim sTime As Double, eTime As Double, verbose As Boolean

    n = CLng(InputBox("How many datasets do you have?", "ENTER # OF DATASETS", "16"))
    r = CLng(InputBox("How many groups do you have?", "ENTER # OF GROUPS", "4"))
    verbose = CBool(InputBox("Should the results be printed?", "VERBOSE OPTION", "True"))

    If Abs(Round(n / r) - (n / r)) > 0.00001 Or r < 2 Or r >= n Then
        MsgBox "Incorrect input!!!"
        '' You could have custom message like: MsgBox "# of Datasets is NOT divisible by # of Groups!!!"
        Exit Sub
    End If

    sTime = Timer
    gSize = n / r
    total = 1

    Dim AllCombs() As Variant, tN As Long
    ReDim AllCombs(1 To r - 1)
    tN = n

    For i = 1 To r - 1
        myRows = nCr(tN, gSize)
        ReDim combos(1 To myRows, 1 To gSize)
        Call GetCombosNoRep(combos, tN, gSize, myRows)
        total = total * myRows / (r - (i - 1))
        AllCombs(i) = combos
        tN = tN - gSize
    Next i

    Dim MasterGroups() As Long
    ReDim MasterGroups(1 To total, 1 To r, 1 To gSize)

    Dim secLength As Long, s As Long, e As Long, m As Long
    secLength = nCr(n, gSize) / r

    Dim v() As Long, child() As Long, q As Long, temp As Long
    ReDim v(1 To n)
    For i = 1 To n: v(i) = i: Next i

    ReDim child(1 To gSize)
    Dim superSecLen As Long, numReps As Long
    superSecLen = total
    Dim endChild() As Long, endV() As Long
    ReDim endChild(1 To n - gSize)
    ReDim endV(1 To gSize)

    '' Populate all but the last 2 columns
    If r > 2 Then
        For i = 1 To r - 2
            numReps = nCr(n - (i - 1) * gSize, gSize) / (r - (i - 1))
            secLength = superSecLen / numReps
            s = 1: e = secLength

            If i = 1 Then
                For j = 1 To numReps
                    For k = s To e
                        For m = 1 To gSize
                            MasterGroups(k, i, m) = v(AllCombs(i)(j, m))
                        Next m
                    Next k
                    s = e + 1
                    e = e + secLength
                Next j
            Else
                ReDim child(1 To (i - 1) * gSize)
                ReDim v(1 To n - (i - 1) * gSize)

                While e < total
                    '' populate child vector so we can get the complement
                    For j = 1 To i - 1
                        For m = 1 To gSize
                            child(m + (j - 1) * gSize) = MasterGroups(s, j, m)
                        Next m
                    Next j

                    Call GetComplement(n, child, v)

                    For q = 1 To numReps
                        For k = s To e
                            For m = 1 To gSize
                                MasterGroups(k, i, m) = v(AllCombs(i)(q, m))
                            Next m
                        Next k
                        s = e + 1
                        e = e + secLength
                    Next q
                Wend
            End If

            superSecLen = secLength
        Next i

        numReps = nCr(n - (r - 2) * gSize, gSize) / (r - 2)
        s = 1: e = secLength

        ReDim child(1 To (r - 2) * gSize)
        ReDim v(1 To n - (r - 2) * gSize)

        While e <= total
            '' populate child vector so we can get the complement
            For j = 1 To r - 2
                For m = 1 To gSize
                    child(m + (j - 1) * gSize) = MasterGroups(s, j, m)
                    endChild(m + (j - 1) * gSize) = MasterGroups(s, j, m)
                Next m
            Next j

            Call GetComplement(n, child, v)
            q = 1

            For k = s To e
                For m = 1 To gSize
                    MasterGroups(k, r - 1, m) = v(AllCombs(r - 1)(q, m))
                    endChild(m + (r - 2) * gSize) = MasterGroups(k, r - 1, m)
                Next m

                q = q + 1
                Call GetComplement(n, endChild, endV)

                For m = 1 To gSize
                    MasterGroups(k, r, m) = endV(m)
                Next m
            Next k
            s = e + 1
            e = e + secLength
        Wend
    Else
        For k = 1 To total
            For m = 1 To gSize
                MasterGroups(k, 1, m) = v(AllCombs(1)(k, m))
                endChild(m) = MasterGroups(k, 1, m)
            Next m

            Call GetComplement(n, endChild, endV)

            For m = 1 To gSize
                MasterGroups(k, 2, m) = endV(m)
            Next m
        Next k
    End If

    If verbose Then
        Dim myString As String, totalString As String, printTotal As Long
        printTotal = Application.WorksheetFunction.Min(100000, total)

        For i = 1 To printTotal
            totalString = vbNullString
            For j = 1 To r
                myString = vbNullString
                For k = 1 To gSize
                    myString = myString & " " & MasterGroups(i, j, k)
                Next k
                myString = Right(myString, Len(myString) - 1)
                myString = "(" & myString & ") "
                totalString = totalString + myString
            Next j
            Cells(i, 1) = totalString
        Next i
        eTime = Timer - sTime
        MsgBox "Generation of " & total & " as well as printing " & printTotal & " custom combinations  completed in : " & eTime & " seconds"
    Else
        eTime = Timer - sTime
        MsgBox "Generation of " & total & " custom combinations completed in : " & eTime & " seconds"
    End If

End Sub

我知道这有点多,但它非常通用且速度相当快。如果您运行 Sub MasterGenerator 并输入 8 作为数据集的数量,并输入 2 作为这样的组数:

你会得到以下结果:

对于 OP 的具体情况,有超过 200 万个结果,因此我们无法将它们全部打印在一列中。但是,使用Verbose = False 运行时,自定义组合会在大约 12 秒内生成。

【讨论】:

  • 这个答案超出了我的预期,非常感谢您简洁、连贯、最重要的是全面的答案。这以一种非常优雅的方式完美解决了我的问题:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-08
  • 1970-01-01
  • 1970-01-01
  • 2016-06-20
  • 2014-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多