Генерация комбинаций нескольких столбцов 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 шт):
Доступ к значениям в ячейках намного медленнее, чем к элементам массива. Поэтому имеет смысл предварительно считать все значения в память. Это можно сделать, например, так:
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, их может быть больше или меньше
- диапазоны с данными могут быть не одной колонкой, а произвольной формы - строками или прямоугольниками
Недостаток - если возможных комбинаций не намного больше, чем ожидаемых результатов, то окончания работы программы можно ожидать довольно долго (постоянно генерируемые дубли отбрасываются словарём и компьютер вынужден многократно повторять бесполезные вычисления)