Doug,
The complete code follows. Unfortunately, I can't post
through DevDex which gives me more horizontal space. The
code will be severely effected by word wrap errors. You'll
have to fix it. I had to remove all indentation to limit
word wrap.
Note:- You must first select the cells containing the
source values before running the macro. The on-the-fly UF
will allow you to input the target value and select filter
criteria. An oversight on my part is that the code does
not advise the user to first select the source data. You
might consider fixing this. It was originally designed
for decimal values such as currency. Try it under a
situation more challenging than just integers.
I use John Walkenbauch's BubbleSort procedure to sort the
array made of the selected numbers. The sorting is done
in memory - the original data is left alone. Please
maintain the credit to John in the code. Please advise of
the outcome.
Hope it goes well.
Regards,
Greg
Option Explicit
Option Base 1
Public Target As Double
Public Tol As Single
Public MaxElem As Integer
Public MaxResults As Integer
Dim List() As Variant, CumList() As Variant, DynList As
Variant
Dim SumVal As Double
Dim num As Integer, RefCell As Range
Dim a As Integer, b As Integer, C As Integer, d As Integer
Dim e As Integer, f As Integer, g As Integer, h As Integer
Dim i As Integer, j As Integer
Sub GW_FindCombinations()
Dim Prompt As String, Title As String, Style As Integer
Dim Resp As Integer, i As Integer, Cell As Range
Call MakeUF 'Create and call user form to get Target value
and filter criteria.
If Target = 0 Then Exit Sub
SumVal = 0
a = 0: b = 0: C = 0: d = 0: e = 0: f = 0: g = 0: h = 0: i
= 0: j = 0
'***** Establish number of elements in list and dimention
arrays *****
num = Selection.Cells.Count + 1 'Additional element in
list to be assigned value of zero.
ReDim List(num)
ReDim CumList(num)
'***** Exit if non-numeric value found in list else assign
selected cell values to list *****
i = 1 'Assign i an initial value of 1 so first value
assigned to array is element 2.
List(1) = 0
CumList(1) = 0
For Each Cell In Selection.Cells
If Not IsNumeric(Cell) Then
MsgBox "Error: Non-numeric value found in the selected
list. " & _
"Only numeric values allowed in list. ",
vbCritical, "Combinations Analysis"
Exit Sub
Else
i = i + 1
List(i) = Cell 'Populate List array with selected elements
leaving first element (item 0) equal to zero.
End If
Next
'***** Sort list in ascending order *****
Call BubbleSort(List())
'***** Establish CumList values as cumulative values of
selected cells *****
For i = 2 To num
CumList(i) = CumList(i - 1) + List(i)
Next
If Target = 0 Then Exit Sub
'***** Calculate maximum number of elements summed
required to exceed Target value *****
For i = 1 To num
If CumList(i) > Target + Tol Then Exit For
Next
'***** Prompt for option to specify max. number of
elements required to sum to Target value *****
If i - 2 > 10 Then
Prompt = "The macro has a limit of 10 elements that can
sum to the target value. It has been determined " & _
"that more than 10 elements from the currently selected
list can sum to " & Target & ". Therefore, you must " & _
"reduce the number of elements in the list, specify a
lower target value or accept an incomplete list of " & _
"results." & vbCr & vbCr & _
"Continue ???"
Style = vbQuestion + vbYesNo
Title = "GW_FindCombinations"
Resp = MsgBox(Prompt, Style, Title)
If Resp = vbNo Then Exit Sub
End If
If MaxElem = 0 Then
Exit Sub
Else
'Format column to right of selection to receive results.
Set RefCell = ActiveCell.Offset(, Selection.Columns.Count)
RefCell.EntireColumn.Insert
Set RefCell = RefCell.Offset(, -1)
With RefCell
..EntireColumn.HorizontalAlignment = 2
..EntireColumn.IndentLevel = 1
..Font.Bold = True
..Value = "Results for Target = " & Target
..Columns.AutoFit
End With
End If
Call MainProc
End Sub
Private Sub MainProc()
Dim z As Integer, NumElem As Integer, NumResults As Integer
Dim Nb As Integer, Nc As Integer, Nd As Integer, Ne As
Integer
Dim Nf As Integer, Ng As Integer, Nh As Integer, Ni As
Integer
Dim Nj As Integer, StartTime As Date, EndTime As Date,
Duration As Variant
Dim Prompt1 As String, Prompt2 As String
Dim Title As String, Style As Integer, Txt As String
StartTime = Now
On Error Resume Next
Application.ScreenUpdating = False
Nb = 0: Nc = 0: Nd = 0: Ne = 0: Nf = 0: Ng = 0: Nh = 0: Ni
= 0: Nj = 0
NumElem = 1
For a = 1 To num: Call CalcSumVal
If SumVal > Target + Tol Then Exit For
For b = a + Nb To num: Call CalcSumVal
If SumVal > Target + Tol Then
b = a + 2: C = a + 3: d = a + 4: e = a + 5: f = a + 6: g =
a + 7: h = a + 8: i = a + 9: j = a + 10
Exit For
End If
For C = b + Nc To num: Call CalcSumVal
If SumVal > Target + Tol Then
C = b + 2: d = b + 3: e = b + 4: f = b + 5: g = b + 6: h =
b + 7: i = b + 8: j = b + 9
Exit For
End If
For d = C + Nd To num: Call CalcSumVal
If SumVal > Target + Tol Then
d = C + 2: e = C + 3: f = C + 4: g = C + 5: h = C + 6: i =
C + 7: j = C + 8
Exit For
End If
For e = d + Ne To num: Call CalcSumVal
If SumVal > Target + Tol Then
e = d + 2: f = d + 3: g = d + 4: h = d + 5: i = d + 6: j =
d + 7
Exit For
End If
For f = e + Nf To num: Call CalcSumVal
If SumVal > Target + Tol Then
f = e + 2: g = e + 3: h = e + 4: i = e + 5: j = e + 6
Exit For
End If
For g = f + Ng To num: Call CalcSumVal
If SumVal > Target + Tol Then
g = f + 2: h = f + 3: i = f + 4: j = f + 5
Exit For
End If
For h = g + Nh To num: Call CalcSumVal
If SumVal > Target + Tol Then
h = g + 2: i = g + 3: j = g + 4
Exit For
End If
For i = h + Ni To num: Call CalcSumVal
If SumVal > Target + Tol Then
i = h + 2: j = h + 3
Exit For
End If
For j = i + Nj To num: Call CalcSumVal
If SumVal > Target + Tol Then
j = i + 2
Exit For
End If
If NumElem > MaxElem Then GoTo EndMsg
If Abs(SumVal - Target) <= Tol Then
For z = 1 To 9
If DynList(z) > 0 Then
Txt = Txt & DynList(z) & " + "
End If
Next
Txt = Txt & DynList(10) & " = " & SumVal
Set RefCell = RefCell.Offset(1)
RefCell.Value = Txt
Txt = ""
NumResults = NumResults + 1
If NumResults = MaxResults Then
MsgBox "Limit of " & MaxResults & " results reached.
Macro aborted. ", _
vbExclamation, "Combinations Analysis"
GoTo EndMsg
End If
End If
Next j: Nj = 1: NumElem = 2
Next i: Ni = 1: NumElem = 3
Next h: Nh = 1: NumElem = 4
Next g: Ng = 1: NumElem = 5
Next f: Nf = 1: NumElem = 6
Next e: Ne = 1: NumElem = 7
Next d: Nd = 1: NumElem = 8
Next C: Nc = 1: NumElem = 9
Next b: Nb = 1: NumElem = 10
Next a
EndMsg:
RefCell.EntireColumn.AutoFit
EndTime = Now
Duration = Format(EndTime - StartTime, "hh:mm:ss")
If NumResults = 0 Then
Prompt1 = "Sorry, no combinations were found that sum to "
& Target & ". " & vbCr & vbCr
Prompt2 = "Duration = " & Duration
Else
Prompt1 = "Analysis complete !!!" & vbCr & vbCr
Prompt2 = "Duration = " & Duration & vbCr & _
"Number of combinations found that sum to " & Target & "
= " & NumResults & " "
End If
Application.ScreenUpdating = True
Style = vbInformation
Title = "GW_FindCombinations"
MsgBox Prompt1 & Prompt2, Style, Title
End Sub
Private Sub CalcSumVal()
DynList = Array(List(a), List(b), List(C), List(d), List
(e), List(f), List(g), List(h), _
List(i), List(j))
SumVal = Application.Sum(DynList)
End Sub
Private Sub BubbleSort(List())
'***** John Walkenback's BubbleSort procedure *****
'***** Do not remove above credit to John in your code
*****
Dim First As Integer, Last As Integer
Dim i As Integer, j As Integer
Dim Temp As Variant
First = LBound(List)
Last = UBound(List)
For i = First To Last - 1
For j = i + 1 To Last
If List(i) > List(j) Then
Temp = List(j)
List(j) = List(i)
List(i) = Temp
End If
Next j
Next i
End Sub
Private Sub MakeUF()
Dim UF As Object, Frame As Object, Ctrl As Object
Dim i As Integer, CM As Object, Line As Integer, Code As
String
Set UF = Application.VBE.ActiveVBProject.VBComponents.Add
(3)
With UF
..Properties("Height") = 175
..Properties("Width") = 160
..Properties("Caption") = "GW_FindCombinations"
End With
Set Ctrl = UF.Designer.Controls.Add("Forms.Label.1")
With Ctrl
..Width = 60
..Height = 18
..Top = 12
..Left = 10
..Caption = "Target value"
End With
Set Ctrl = UF.Designer.Controls.Add("Forms.Textbox.1")
With Ctrl
..Width = 40
..Height = 16
..Top = 10
..Left = 80
..Font.Size = 8
End With
Set Frame = UF.Designer.Controls.Add("Forms.Frame.1")
With Frame
..Width = 145
..Height = 90
..Top = 30
..Left = 5
..Caption = "Filter"
End With
For i = 1 To 5 Step 2
Set Ctrl = Frame.Controls.Add("Forms.Label.1")
With Ctrl
..Width = 70
..Height = 18
..Top = i * 12 + 2
..Left = 5
Select Case i
Case 1
..Caption = "Tolerance (±) Pct"
Case 3
..Caption = "Max. Elements"
Case 5
..Caption = "Max. Results"
End Select
End With
Next
For i = 1 To 5 Step 2
Set Ctrl = Frame.Controls.Add("Forms.Textbox.1")
With Ctrl
..Width = 35
..Height = 16
..Top = i * 12
..Left = 80
..Font.Size = 8
Select Case i
Case 1
..Text = "0.00"
Case 3
..Text = "10"
Case 5
..Text = "1000"
End Select
End With
Next
For i = 1 To 5 Step 2
Set Ctrl = Frame.Controls.Add("Forms.Spinbutton.1")
With Ctrl
..Orientation = 0
..Width = 15
..Height = 16
..Top = i * 12
..Left = 120
End With
Next
For i = 0 To 1
Set Ctrl = UF.Designer.Controls.Add
("Forms.CommandButton.1")
With Ctrl
..Width = 60
..Height = 18
..Top = 130
..Left = 12 + i * 70
If i = 0 Then .Caption = "OK" Else .Caption = "Abort"
End With
Next
Set CM = UF.CodeModule
With CM
Line = CM.CountOfLines
Code = "Private Sub SpinButton1_SpinUp()"
Code = Code & vbCr & "With TextBox2"
Code = Code & vbCr & ".Text = WorksheetFunction.Min(Val
(.Text) + 0.01, 5)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub SpinButton1_SpinDown()"
Code = Code & vbCr & "With TextBox2"
Code = Code & vbCr & ".Text = WorksheetFunction.Max(Val
(.Text) - 0.01, 0)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub SpinButton2_SpinUp()"
Code = Code & vbCr & "With TextBox3"
Code = Code & vbCr & ".Text = WorksheetFunction.Min(Val
(.Text) + 1, 10)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub SpinButton2_SpinDown()"
Code = Code & vbCr & "With TextBox3"
Code = Code & vbCr & ".Text = WorksheetFunction.Max(Val
(.Text) - 1, 1)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub SpinButton3_SpinUp()"
Code = Code & vbCr & "With TextBox4"
Code = Code & vbCr & ".Text = WorksheetFunction.Min(Val
(.Text) + 1, 1000)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub SpinButton3_SpinDown()"
Code = Code & vbCr & "With TextBox4"
Code = Code & vbCr & ".Text = WorksheetFunction.Max(Val
(.Text) - 1, 1)"
Code = Code & vbCr & "End With"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub CommandButton1_Click()"
Code = Code & vbCr & "Target = Val(TextBox1.Text)"
Code = Code & vbCr & "Tol = TextBox1.Value * Val
(TextBox2.Text) / 100"
Code = Code & vbCr & "MaxElem = Val(TextBox3.Text)"
Code = Code & vbCr & "MaxResults = Val(TextBox4.Text)"
Code = Code & vbCr & "Unload Me"
Code = Code & vbCr & "End Sub"
Code = Code & vbCr & "Private Sub CommandButton2_Click()"
Code = Code & vbCr & "Unload Me"
Code = Code & vbCr & "End Sub"
CM.InsertLines Line + 1, Code
End With
VBA.UserForms.Add(UF.Name).Show
ThisWorkbook.VBProject.VBComponents.Remove UF
End Sub