Генерация комбинаций нескольких столбцов Excel с помощью VBA

Подскажите пожалуйста, у меня есть такой код который генерит все возможные комбинции из заданных значений в столбцах в Excel. Если ли возможность брать рандомые строки и лимитировать количество резльтатов до 10000 к примеру? Спасибо

Sub ListAllCombinations()
'Updateby Extendoffice
Dim xDRg1, xDRg2, xDRg3, xDRg4, xDRg5, xDRg6, xDRg7, xDRg8, xDRg9, xDRg0 As Range
Dim xRg  As Range
Dim xStr As String
Dim xFN1, xFN2, xFN3, xFN4, xFN5, xFN6, xFN7, xFN8, xFN9, xFN0  As Integer
Dim xSV1, xSV2, xSV3, xSV4, xSV5, xSV6, xSV7, xSV8, xSV9, xSV0 As String
Set xDRg1 = Range("A2:A8")  'First column data
Set xDRg2 = Range("C2:C170")  'Second column data
Set xDRg3 = Range("E2:E178")  'Third column data
Set xDRg4 = Range("G2:G35")
Set xDRg5 = Range("I2:I18")
Set xDRg6 = Range("K2:K15")
Set xDRg7 = Range("M2:M15")
Set xDRg8 = Range("O2:O10")
Set xDRg9 = Range("X2:X16")
Set xDRg0 = Range("Z2:Z3")
xStr = "-"   'Separator
Set xRg = Range("AF2")  'Output cell
For xFN1 = 1 To xDRg1.Count
    xSV1 = xDRg1.Item(xFN1).Text
    For xFN2 = 1 To xDRg2.Count
        xSV2 = xDRg2.Item(xFN2).Text
      For xFN3 = 1 To xDRg3.Count
          xSV3 = xDRg3.Item(xFN3).Text
            For xFN4 = 1 To xDRg4.Count
                xSV4 = xDRg4.Item(xFN4).Text
                For xFN5 = 1 To xDRg5.Count
                    xSV5 = xDRg5.Item(xFN5).Text
                    For xFN6 = 1 To xDRg6.Count
                        xSV6 = xDRg6.Item(xFN6).Text
                        For xFN7 = 1 To xDRg7.Count
                            xSV7 = xDRg7.Item(xFN7).Text
                            For xFN8 = 1 To xDRg8.Count
                                xSV8 = xDRg8.Item(xFN8).Text
                                For xFN9 = 1 To xDRg9.Count
                                    xSV9 = xDRg9.Item(xFN9).Text
                                    For xFN0 = 1 To xDRg0.Count
                                        xSV0 = xDRg0.Item(xFN0).Text
                                        xRg.Value = xSV1 & xStr & xSV2 & xStr & xSV3 & xStr & xSV4 & xStr & xSV5 & xStr & xSV6 & xStr & xSV7 & xStr & xSV8 & xStr & xSV9 & xStr & xSV0
                                        Set xRg = xRg.Offset(1, 0)
                                    Next
                                Next
                             Next
                        Next
                    Next
                Next
             Next
        Next
    Next
Next
End Sub


Ответы (1 шт):

Автор решения: JohnSUN

Доступ к значениям в ячейках намного медленнее, чем к элементам массива. Поэтому имеет смысл предварительно считать все значения в память. Это можно сделать, например, так:

Function getAllSourceData(ByRef dTotalCombinations As Double) As Variant
Dim aRngs As Variant
Dim aRes As Variant
Dim i As Integer
    aRngs = Split(ALL_DATA_RANGES, ";")
    dTotalCombinations = 1
    ReDim aRes(LBound(aRngs) To UBound(aRngs))
    For i = LBound(aRngs) To UBound(aRngs)
        aRes(i) = getDataFromRange(aRngs(i))
        dTotalCombinations = dTotalCombinations * (UBound(aRes(i)) + 1)
    Next i
    getAllSourceData = aRes
End Function

Function getDataFromRange(ByVal sRange As String) As Variant
Dim oRange As Range
Dim oCell As Range
Dim aRes As Variant
Dim i As Long
    Set oRange = Range(sRange)
    ReDim aRes(0 To oRange.Cells.Count - 1)
    i = 0
    For Each oCell In oRange.Cells
        aRes(i) = oCell.Text
        i = i + 1
    Next oCell
    getDataFromRange = aRes
End Function

В результате будет создан массив, каждый из элементов которого - значения строк каждого из поддиапазонов (тоже массивы). Кроме того функция getAllSourceData() через параметр вернёт общее количество комбинаций, которые можно создать из собранных данных.

Получить одну случайную комбинацию из такой структуры можно так:

Function getNextString(aSourceData As Variant) As String
Const SEPARATOR As String = "-"
Dim i As Long
Dim sRes() As String
Dim Index As Long
    ReDim sRes(LBound(aSourceData) To UBound(aSourceData))
    For i = LBound(aSourceData) To UBound(aSourceData)
        Index = WorksheetFunction.RandBetween(LBound(aSourceData(i)), UBound(aSourceData(i)))
        sRes(i) = aSourceData(i)(Index)
    Next i
    getNextString = Join(sRes, SEPARATOR)
End Function

С этими тремя функциями можно собрать главную процедуру:

Sub ListPartCombinations()
Dim aData As Variant            
Dim oTargetCell As Range        
Dim dCount As Double            
Dim dictResults As Object       
Dim sRes As Variant             
    aData = getAllSourceData(dCount)
    If dCount < LAST_ROW Then
        Call MsgBox(("Исходных данных не хватит для генерации " & LAST_ROW & " значений" _
            & Chr(10) & "Воспользуйтесь алгоритмом ListAllCombinations()", _
            vbOKOnly + vbInformation, "Мало данных")
        Exit Sub
    End If
    Set dictResults = CreateObject("Scripting.Dictionary")
    While dictResults.Count <= LAST_ROW
        dictResults.Item(getNextString(aData)) = 0
    Wend
    Set oTargetCell = Range(FIRST_OUTPUT_CELL)
    For Each sRes In dictResults.keys
        oTargetCell.Value = sRes
        Set oTargetCell = oTargetCell.Offset(1, 0)
    Next sRes
End Sub

Для вывода накопленных значений в ячейки листа можно использовать Transpose(), но для очень больших наборов этот способ может не работать правильно.

Теперь достаточно добавить в заголовок модуля строки:

Option Explicit
Const ALL_DATA_RANGES = "A2:A8;C2:C170;E2:E178;G2:G35;I2:I18;K2:K15;M2:M15;O2:O10;X2:X16;Z2:Z3"
Const FIRST_OUTPUT_CELL = "AF2"
Const LAST_ROW As Long = 10000  

и программа готова к использованию.

Преимущества такого подхода:

  • диапазонов с данными не обязательно должно быть ровно 10, их может быть больше или меньше
  • диапазоны с данными могут быть не одной колонкой, а произвольной формы - строками или прямоугольниками

Недостаток - если возможных комбинаций не намного больше, чем ожидаемых результатов, то окончания работы программы можно ожидать довольно долго (постоянно генерируемые дубли отбрасываются словарём и компьютер вынужден многократно повторять бесполезные вычисления)

→ Ссылка