2

私はこの質問をもう一度、少し異なる文脈で行うよう求められました。これは以前の投稿です:

組み合わせを見つけた後のVBAでのフィルタリング

Excel のメモリ不足や実行時間を大幅に短縮することなく、100 の異なる変数を使用してこのコードを実行できるようにしたいと考えています。

以下のコードの問題は、100 個のボックスがある場合、"Result(0 To 2 ^ NumFields - 2)" という行で Excel がメモリ不足になることです (コードは 10 個未満のボックスで機能します)。

これは私の入力です:

3   A   B   C   D   E ...
7.7 3   1   1   1   2 ...
5.5 2   1   2   3   3 ...

これはコードです:

Function stackBox()
    Dim ws As Worksheet
    Dim width As Long
    Dim height As Long
    Dim numOfBox As Long
    Dim optionsA() As Variant
    Dim results() As Variant
    Dim str As String
    Dim outputArray As Variant
    Dim i As Long, j As Long
    Dim currentSymbol As String
    '------------------------------------new part----------------------------------------------
    Dim maxHeight As Double
    Dim maxWeight As Double
    Dim heightarray As Variant
    Dim weightarray As Variant
    Dim totalHeight As Double
    Dim totalWeight As Double
    '------------------------------------new part----------------------------------------------

    Set ws = Worksheets("Sheet1")
    With ws
        'clear last time's output
        height = .Cells(.Rows.Count, 1).End(xlUp).row
        If height > 3 Then
            .Range(.Cells(4, 1), .Cells(height, 1)).ClearContents
        End If

        numOfBox = .Cells(1, 1).Value
        width = .Cells(1, .Columns.Count).End(xlToLeft).Column
        If width < 2 Then
            MsgBox "Error: There's no item, please fill your item in Cell B1,C1,..."
            Exit Function
        End If


        '------------------------------------new part----------------------------------------------
        maxHeight = .Cells(2, 1).Value
        maxWeight = .Cells(3, 1).Value
        ReDim heightarray(1 To 1, 1 To width - 1)
        ReDim weightarray(1 To 1, 1 To width - 1)
        heightarray = .Range(.Cells(2, 2), .Cells(2, width)).Value
        weightarray = .Range(.Cells(3, 2), .Cells(3, width)).Value
        '------------------------------------new part----------------------------------------------

        ReDim optionsA(0 To width - 2)
        For i = 0 To width - 2
            optionsA(i) = .Cells(1, i + 2).Value
        Next i

        GenerateCombinations optionsA, results, numOfBox


        ' copy the result to sheet only once
        ReDim outputArray(1 To UBound(results, 1) - LBound(results, 1) + 1, 1 To 1)
        Count = 0
        For i = LBound(results, 1) To UBound(results, 1)
            If Not IsEmpty(results(i)) Then
                'rowNum = rowNum + 1
                str = ""
                totalHeight = 0#
                totalWeight = 0#
                For j = LBound(results(i), 1) To UBound(results(i), 1)
                    currentSymbol = results(i)(j)

                    str = str & currentSymbol 'results(i)(j) is the SYMBOL e.g. A, B, C

                    'look up box's height and weight , increment the totalHeight/totalWeight
                    updateParam currentSymbol, optionsA, heightarray, weightarray, totalHeight, totalWeight

                Next j
                If totalHeight < maxHeight And totalWeight < maxWeight Then
                    Count = Count + 1
                    outputArray(Count, 1) = str
                End If

            '.Cells(rowNum, 1).Value = str
            End If
        Next i
        .Range(.Cells(4, 1), .Cells(UBound(outputArray, 1) + 3, 1)).Value = outputArray
    End With

End Function

Sub updateParam(ByRef targetSymbol As String, ByRef symbolArray As Variant, ByRef heightarray As Variant, ByRef weightarray As Variant, ByRef totalHeight As Double, ByRef totalWeight As Double)
Dim i As Long
Dim index As Long
index = -1
For i = LBound(symbolArray, 1) To UBound(symbolArray, 1)
    If targetSymbol = symbolArray(i) Then
        index = i
        Exit For
    End If
Next i


If index <> -1 Then
    totalHeight = totalHeight + heightarray(1, index + 1)
    totalWeight = totalWeight + weightarray(1, index + 1)
End If
End Sub

Sub GenerateCombinations(ByRef AllFields() As Variant, _
                                             ByRef Result() As Variant, ByVal numOfBox As Long)

  Dim InxResultCrnt As Integer
  Dim InxField As Integer
  Dim InxResult As Integer
  Dim i As Integer
  Dim NumFields As Integer
  Dim Powers() As Integer
  Dim ResultCrnt() As String

  NumFields = UBound(AllFields) - LBound(AllFields) + 1

  ReDim Result(0 To 2 ^ NumFields - 2)  ' one entry per combination
  ReDim Powers(0 To NumFields - 1)          ' one entry per field name

  ' Generate powers used for extracting bits from InxResult
  For InxField = 0 To NumFields - 1
    Powers(InxField) = 2 ^ InxField
  Next

 For InxResult = 0 To 2 ^ NumFields - 2
    ' Size ResultCrnt to the max number of fields per combination
    ' Build this loop's combination in ResultCrnt

    ReDim ResultCrnt(0 To NumFields - 1)
    InxResultCrnt = -1
    For InxField = 0 To NumFields - 1
      If ((InxResult + 1) And Powers(InxField)) <> 0 Then
        ' This field required in this combination
        InxResultCrnt = InxResultCrnt + 1
        ResultCrnt(InxResultCrnt) = AllFields(InxField)
      End If
    Next

    If InxResultCrnt = 0 Then
        Debug.Print "testing"
    End If
    'additional logic here
    If InxResultCrnt >= numOfBox Then
        Result(InxResult) = Empty

    Else
         ' Discard unused trailing entries
        ReDim Preserve ResultCrnt(0 To InxResultCrnt)
        ' Store this loop's combination in return array
        Result(InxResult) = ResultCrnt
    End If

  Next

End Sub
4

1 に答える 1

1

これは、バリアント配列ですべての面倒な作業を行うバージョンです

( Joubarcによるこの回答に対するこの回答に基づく組み合わせロジック)

これは 100 個のボックスのサンプル データセットで実行され、40,000 個以上が返され、1 秒未満で実行されます

ノート:

  1. ボックスの最大数が増えると、実行時間は急激に増加します (例: 100 から 4: 約 13 秒)。
  2. 返される結果の数が 65535 を超える場合、配列をシートに転置するコードは失敗します (サブの最後の行) これを処理する必要がある場合は、結果がシートに返される方法を変更する必要があります。

Sub Demo()
    Dim rNames As Range
    Dim rHeights As Range
    Dim rWeights As Range

    Dim aNames As Variant
    Dim aHeights As Variant
    Dim aWeights As Variant

    Dim MaxNum As Long
    Dim MaxHeight As Double
    Dim MaxWeight As Double

    ' *** replace these six line with your data ranges
    Set rNames = Range([F5], [F5].End(xlToRight))
    Set rHeights = rNames.Offset(1, 0)
    Set rWeights = rNames.Offset(2, 0)
    MaxNum = [C5]
    MaxHeight = [C6]
    MaxWeight = [C7]

    aNames = rNames
    aHeights = rHeights
    aWeights = rWeights

    Dim Result() As Variant
    Dim n As Long, m As Long
    Dim i As Long, j As Long
    Dim iRes As Long
    Dim res As String
    Dim TestCombin() As Long
    Dim TestWeight As Double
    Dim TestHeight As Double
    Dim idx() As Long

    ' Number of boxes
    ReDim TestCombin(0 To MaxNum - 1)
    n = UBound(aNames, 2) - LBound(aNames, 2) + 1

    ' estimate size of result array = number of possible combinations
    For m = 1 To MaxNum
        i = i + Application.WorksheetFunction.Combin(n, m)
    Next
    ReDim Result(1 To 3, 1 To i)

    ' allow for from 1 to MaxNum of boxes
    iRes = 1
    For m = 1 To MaxNum
        ReDim idx(0 To m - 1)
        For i = 0 To m - 1
            idx(i) = i
        Next i

        Do
            'Test current combination
            res = ""
            TestWeight = 0#
            TestHeight = 0#
            For j = 0 To m - 1
                'Debug.Print aNames(1, idx(j) + 1);
                res = res & aNames(1, idx(j) + 1)
                TestWeight = TestWeight + aWeights(1, idx(j) + 1)
                TestHeight = TestHeight + aHeights(1, idx(j) + 1)
            Next j
            'Debug.Print
            If TestWeight <= MaxWeight And TestHeight <= MaxHeight Then
                Result(1, iRes) = res
                ' optional, include actual Height and Weight in result
                Result(2, iRes) = TestHeight
                Result(3, iRes) = TestWeight
                iRes = iRes + 1
            End If

            ' Locate last non-max index
            i = m - 1
            While (idx(i) = n - m + i)
                i = i - 1
                If i < 0 Then
                    'All indexes have reached their max, so we're done
                    Exit Do
                End If
            Wend

            'Increase it and populate the following indexes accordingly
            idx(i) = idx(i) + 1
            For j = i To m - 1
                idx(j) = idx(i) + j - i
            Next j
        Loop
    Next

    ' Return Result to sheet    
    Dim rng As Range
    ReDim Preserve Result(1 To 3, 1 To iRes)

    ' *** Adjust returnm range to suit
    Set rng = [E10].Resize(UBound(Result, 2), UBound(Result, 1))
    rng = Application.Transpose(Result)
End Sub
于 2012-11-02T10:52:03.963 に答える