#vba #loops #optimization
Вопрос:
Я запускаю приведенный ниже код, и на его выполнение уходит около 8 часов. Он выбирает 20 проектов (из 38), максимизируя полезность проектов при сохранении бюджетных ограничений. Существует также ограничение на минимальное/максимальное количество проектов класса A, класса B и класса A и класса B вместе взятых.
Код работает нормально, однако для его завершения требуется много времени. Знаете ли вы, как оптимизировать этот код, чтобы он работал быстрее (если это возможно)?
»’
Sub CalculateOptions()
Dim InputProjects(1 To 38, 4) As Single
Dim TopChoices(1 To 5, 1 To 22) As Single
Dim CurrentChoice(1 To 22) As Single
TotalHorInArray = 22
TotalVertInArray = 5
MinimalProjectsClassA = 9
MaximalProjectsClassA = 14
MinimalProjectsClassB = 5
MaximalProjectsClassB = 9
MinimalProjectsClassAandClassB = 15
MaximalProjectsClassAandClassB = 19
Budget = 49
NumberOfProjects = 38
'Read input
For x = 1 To NumberOfProjects
For y = 1 To 4
InputProjects(x, y) = Cells(2 x, 4 y)
Next
Next
For a = 1 To NumberOfProjects
For b = a 1 To NumberOfProjects
For c = b 1 To NumberOfProjects
For d = c 1 To NumberOfProjects
For e = d 1 To NumberOfProjects
For f = e 1 To NumberOfProjects
For g = f 1 To NumberOfProjects
For h = g 1 To NumberOfProjects
For i = h 1 To NumberOfProjects
For j = i 1 To NumberOfProjects
For k = j 1 To NumberOfProjects
For l = k 1 To NumberOfProjects
For m = l 1 To NumberOfProjects
For n = m 1 To NumberOfProjects
For o = n 1 To NumberOfProjects
For p = o 1 To NumberOfProjects
For q = p 1 To NumberOfProjects
For r = q 1 To NumberOfProjects
For s = r 1 To NumberOfProjects
For t = s 1 To NumberOfProjects
' Utility
UtilityChoice = InputProjects(a, 1) _
InputProjects(b, 1) _
InputProjects(c, 1) _
InputProjects(d, 1) _
InputProjects(e, 1) _
InputProjects(f, 1) _
InputProjects(g, 1) _
InputProjects(h, 1) _
InputProjects(i, 1) _
InputProjects(j, 1) _
InputProjects(k, 1) _
InputProjects(l, 1) _
InputProjects(m, 1) _
InputProjects(n, 1) _
InputProjects(o, 1) _
InputProjects(p, 1) _
InputProjects(q, 1) _
InputProjects(r, 1) _
InputProjects(s, 1) _
InputProjects(t, 1)
' Check if utility constraint is met (only want to show the top 5 combinations in the output)
If UtilityChoice > max5 Then
' Budget
budgetChoice = InputProjects(a, 3) _
InputProjects(b, 3) _
InputProjects(c, 3) _
InputProjects(d, 3) _
InputProjects(e, 3) _
InputProjects(f, 3) _
InputProjects(g, 3) _
InputProjects(h, 3) _
InputProjects(i, 3) _
InputProjects(j, 3) _
InputProjects(k, 3) _
InputProjects(l, 3) _
InputProjects(m, 3) _
InputProjects(n, 3) _
InputProjects(o, 3) _
InputProjects(p, 3) _
InputProjects(q, 3) _
InputProjects(r, 3) _
InputProjects(s, 3) _
InputProjects(t, 3)
' Check if budget constraint is met
If budgetChoice <= Budget Then
' Check number of projects per Class
ClassOfProject = InputProjects(a, 4) _
InputProjects(b, 4) _
InputProjects(c, 4) _
InputProjects(d, 4) _
InputProjects(e, 4) _
InputProjects(f, 4) _
InputProjects(g, 4) _
InputProjects(h, 4) _
InputProjects(i, 4) _
InputProjects(j, 4) _
InputProjects(k, 4) _
InputProjects(l, 4) _
InputProjects(m, 4) _
InputProjects(n, 4) _
InputProjects(o, 4) _
InputProjects(p, 4) _
InputProjects(q, 4) _
InputProjects(r, 4) _
InputProjects(s, 4) _
InputProjects(t, 4) 10
ProjectsClassA = Right(ClassOfProject, 2) - 10
ProjectsClassB = Round(ClassOfProject / 100, 0)
ProjectsClassAorB = ProjectsClassA ProjectsClassB
' Check if minumum and maximum constraint is satisfied for total projects of class A and B
If ProjectsClassA >= MinimalProjectsClassA And ProjectsClassA <= MaximalProjectsClassA _
And ProjectsClassB >= MinimalProjectsClassB And ProjectsClassB <= MaximalProjectsClassB And _
ProjectsClassAorB >= MinimalProjectsClassAandClassB And ProjectsClassAorB <= MaximalProjectsClassAandClassB Then
' Project specifics
CurrentChoice(1) = InputProjects(a, 2)
CurrentChoice(2) = InputProjects(b, 2)
CurrentChoice(3) = InputProjects(c, 2)
CurrentChoice(4) = InputProjects(d, 2)
CurrentChoice(5) = InputProjects(e, 2)
CurrentChoice(6) = InputProjects(f, 2)
CurrentChoice(7) = InputProjects(g, 2)
CurrentChoice(8) = InputProjects(h, 2)
CurrentChoice(9) = InputProjects(i, 2)
CurrentChoice(10) = InputProjects(j, 2)
CurrentChoice(11) = InputProjects(k, 2)
CurrentChoice(12) = InputProjects(l, 2)
CurrentChoice(13) = InputProjects(m, 2)
CurrentChoice(14) = InputProjects(n, 2)
CurrentChoice(15) = InputProjects(o, 2)
CurrentChoice(16) = InputProjects(p, 2)
CurrentChoice(17) = InputProjects(q, 2)
CurrentChoice(18) = InputProjects(r, 2)
CurrentChoice(19) = InputProjects(s, 2)
CurrentChoice(20) = InputProjects(t, 2)
CurrentChoice(21) = UtilityChoice
CurrentChoice(22) = budgetChoice
' Read in project specifics if utility is in the top 5 (and constraints above are met)
If UtilityChoice > max1 Then
max5 = max4
max4 = max3
max3 = max2
max2 = max1
max1 = UtilityChoice
For Z = 1 To TotalHorInArray
TopChoices(5, Z) = TopChoices(4, Z)
TopChoices(4, Z) = TopChoices(3, Z)
TopChoices(3, Z) = TopChoices(2, Z)
TopChoices(2, Z) = TopChoices(1, Z)
TopChoices(1, Z) = CurrentChoice(Z)
Next
ElseIf UtilityChoice > max2 Then
max5 = max4
max4 = max3
max3 = max2
max2 = UtilityChoice
For Z = 1 To TotalHorInArray
TopChoices(5, Z) = TopChoices(4, Z)
TopChoices(4, Z) = TopChoices(3, Z)
TopChoices(3, Z) = TopChoices(2, Z)
TopChoices(2, Z) = CurrentChoice(Z)
Next
ElseIf UtilityChoice > max3 Then
max5 = max4
max4 = max3
max3 = UtilityChoice
For Z = 1 To TotalHorInArray
TopChoices(5, Z) = TopChoices(4, Z)
TopChoices(4, Z) = TopChoices(3, Z)
TopChoices(3, Z) = CurrentChoice(Z)
Next
ElseIf UtilityChoice > max4 Then
max5 = max4
max4 = UtilityChoice
For Z = 1 To TotalHorInArray
TopChoices(5, Z) = TopChoices(4, Z)
TopChoices(4, Z) = CurrentChoice(Z)
Next
ElseIf UtilityChoice > max5 Then
max5 = UtilityChoice
For Z = 1 To TotalHorInArray
TopChoices(5, Z) = CurrentChoice(Z)
Next
End If
End If
End If
End If
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
Next
' Write output to excel
For x = 1 To TotalVertInArray
For y = 1 To TotalHorInArray
Range("OutputCalculation").Offset(x, y) = TopChoices(x, y)
Next
Next
Range("A1").Select
End Sub
»’
Спасибо,
Вальтер
Комментарии:
1. В разделе «Запись выходных данных в excel» вы можете просто поместить массив в диапазон. Вы знаете, где его задерживают? Занимает ли время до «Записи выходных данных в excel», например, 1-й выходной?
2. С такой глубиной цикла удивительно, что запуск вашего кода занимает всего несколько часов. Лучший способ повысить производительность-выбросить этот код и использовать радикально иной подход, который использует решатель для решения хорошо сформулированной задачи дискретной оптимизации. Проблема не кажется огромной (с точки зрения исследования операций). Лучший подход может занять всего несколько секунд.
3. Поскольку только на самой внутренней итерации
t
происходит изменение, вы можете разместить все остальные обращения за пределами этого цикла. Продолжайте со следующим самым внутренним циклом. Немного размышлений должно привести к более эффективному коду.4. Я не хотел показаться таким резким в своем последнем комментарии, но основная проблема заключается в том, что ваш основной алгоритм-это поиск методом перебора с(38,20) = 33,6 миллиарда возможностей. Несомненно, вы могли бы сделать этот поиск грубой силы более эффективным поиском грубой силы, но 33,6 миллиарда-это все равно 33,6 миллиарда, а VBA не демон скорости. С другой стороны, это задача целочисленного программирования 0-1 такого рода, с которой решатель может справиться с помощью сложных алгоритмов, реализованных в чем-то вроде C. Если вы хотите действительно значительного ускорения, вам действительно нужен совсем другой подход.
5. Совет искать лучшее решение вполне обоснован. Однако в этом случае вы говорите, что ваш код работает, вам просто нужно, чтобы он работал быстрее. В таком случае я бы рекомендовал взглянуть на twinbasic. Тем временем установите бесплатную и фантастическую надстройку Rubberduck для VBA и внимательно ознакомьтесь с проверками кода, которые она предоставляет.