MS Office Forum / Excel / New Users / April 2008
Pick several numbers from a list to get as near as possible to required total
|
|
Thread rating:  |
GerryGerry - 11 Mar 2008 14:41 GMT I have a list of several hundred policies each with a different value. Occasionally I have a request to 'sell off' policies to a certain value. At the moment I manually select policies from the list till I get 'close enough' to the total. Is there a way of automating this and getting the closest result possible?
To put numbers to my problem above, suppose I have the following 9 policies
1 $11,234.67 2 $604.50 3 $7,632.00 4 $5,638.76 5 $16,345.98 6 $23,678.43 7 $15,678.44 8 $1,007.17 9 $53,713.97
I get a request to sell of $54,500 worth, at a glance I would probably select policies 8 & 9 (totaling $54,721.14), where as infact policies 3, 4, 5, 6 & 8 would be a better choice as they total $54,302.54
All help would be much appreciated
Gerry
Niek Otten - 11 Mar 2008 15:33 GMT Hi Gerry,
Copied from my archive
 Signature Kind regards,
Niek Otten Microsoft MVP - Excel
Find numbers that add up to a specified sum. Niek Otten 05-Apr-06
This type of application tends to be very resource-consuming. It is wise to test a solution first with a limited set of data One option is using Solver; I include an example given by MVP Peo Sjoblom. The other is a rather famous VBA Sub by Harlan Grove. There seems to be one flaw: if the table is sorted ascending and the first n numbers sum up to the required value exactly, it will miss that combination. I don't know if this has been corrected later. Note the requirements for your settings documented in the code itself
Peo's solution: ================================================== One way but you need the solver add-in installed (it comes with excel/office,check under tools>add-ins) put the data set in let's say A2:A8, in B2:B8 put a set of ones {1,1,1 etc} in the adjacent cells in C2 put 8, in D2 put =SUMPRODUCT(A2:A7,B2:B7) select D2 and do tools>solver, set target cell $D$2 (should come up automatically if selected) Equal to a Value of 8, by changing cells $B$2:$B$7, click add under Subject to the constraints of: in Cell reference put $B$2:$B$7 from dropdown select Bin, click OK and click Solve, Keep solver solution and look at the table 2 1 4 0 5 0 6 1 9 0 13 0 there you can see that 4 ones have been replaced by zeros and the adjacent cells to the 2 ones total 8 -- Regards, Peo Sjoblom ================================================== Harlan's solution:
'Begin VBA Code
' By Harlan Grove
Sub findsums() 'This *REQUIRES* VBAProject references to 'Microsoft Scripting Runtime 'Microsoft VBScript Regular Expressions 1.0 or higher
Const TOL As Double = 0.000001 'modify as needed Dim c As Variant
Dim j As Long, k As Long, n As Long, p As Boolean Dim s As String, t As Double, u As Double Dim v As Variant, x As Variant, y As Variant Dim dc1 As New Dictionary, dc2 As New Dictionary Dim dcn As Dictionary, dco As Dictionary Dim re As New RegExp
re.Global = True re.IgnoreCase = True
On Error Resume Next
Set x = Application.InputBox( _ Prompt:="Enter range of values:", _ Title:="findsums", _ Default:="", _ Type:=8 _ )
If x Is Nothing Then Err.Clear Exit Sub End If
y = Application.InputBox( _ Prompt:="Enter target value:", _ Title:="findsums", _ Default:="", _ Type:=1 _ )
If VarType(y) = vbBoolean Then Exit Sub Else t = y End If
On Error GoTo 0
Set dco = dc1 Set dcn = dc2
Call recsoln
For Each y In x.Value2 If VarType(y) = vbDouble Then If Abs(t - y) < TOL Then recsoln "+" & Format(y)
ElseIf dco.Exists(y) Then dco(y) = dco(y) + 1
ElseIf y < t - TOL Then dco.Add Key:=y, Item:=1
c = CDec(c + 1) Application.StatusBar = "[1] " & Format(c)
End If
End If Next y
n = dco.Count
ReDim v(1 To n, 1 To 3)
For k = 1 To n v(k, 1) = dco.Keys(k - 1) v(k, 2) = dco.Items(k - 1) Next k
qsortd v, 1, n
For k = n To 1 Step -1 v(k, 3) = v(k, 1) * v(k, 2) + v(IIf(k = n, n, k + 1), 3) If v(k, 3) > t Then dcn.Add Key:="+" & _ Format(v(k, 1)), Item:=v(k, 1) Next k
On Error GoTo CleanUp Application.EnableEvents = False Application.Calculation = xlCalculationManual
For k = 2 To n dco.RemoveAll swapo dco, dcn
For Each y In dco.Keys p = False
For j = 1 To n If v(j, 3) < t - dco(y) - TOL Then Exit For x = v(j, 1) s = "+" & Format(x) If Right(y, Len(s)) = s Then p = True If p Then re.Pattern = "\" & s & "(?=(\+|$))" If re.Execute(y).Count < v(j, 2) Then u = dco(y) + x If Abs(t - u) < TOL Then recsoln y & s ElseIf u < t - TOL Then dcn.Add Key:=y & s, Item:=u c = CDec(c + 1) Application.StatusBar = "[" & Format(k) & "] " & _ Format(c) End If End If End If Next j Next y
If dcn.Count = 0 Then Exit For Next k
If (recsoln() = 0) Then _ MsgBox Prompt:="all combinations exhausted", _ Title:="No Solution"
CleanUp: Application.EnableEvents = True Application.Calculation = xlCalculationAutomatic Application.StatusBar = False
End Sub
Private Function recsoln(Optional s As String) Const OUTPUTWSN As String = "findsums solutions" 'modify to taste
Static r As Range Dim ws As Worksheet
If s = "" And r Is Nothing Then On Error Resume Next Set ws = ActiveWorkbook.Worksheets(OUTPUTWSN) If ws Is Nothing Then Err.Clear Application.ScreenUpdating = False Set ws = ActiveSheet Set r = Worksheets.Add.Range("A1") r.Parent.Name = OUTPUTWSN ws.Activate Application.ScreenUpdating = False Else ws.Cells.Clear Set r = ws.Range("A1") End If recsoln = 0 ElseIf s = "" Then recsoln = r.Row - 1 Set r = Nothing Else r.Value = s Set r = r.Offset(1, 0) recsoln = r.Row - 1 End If End Function
Private Sub qsortd(v As Variant, lft As Long, rgt As Long) 'ad hoc quicksort subroutine 'translated from Aho, Weinberger & Kernighan, '"The Awk Programming Language", page 161
Dim j As Long, pvt As Long
If (lft >= rgt) Then Exit Sub swap2 v, lft, lft + Int((rgt - lft + 1) * Rnd) pvt = lft For j = lft + 1 To rgt If v(j, 1) > v(lft, 1) Then pvt = pvt + 1 swap2 v, pvt, j End If Next j
swap2 v, lft, pvt
qsortd v, lft, pvt - 1 qsortd v, pvt + 1, rgt End Sub
Private Sub swap2(v As Variant, i As Long, j As Long) 'modified version of the swap procedure from 'translated from Aho, Weinberger & Kernighan, '"The Awk Programming Language", page 161
Dim t As Variant, k As Long
For k = LBound(v, 2) To UBound(v, 2) t = v(i, k) v(i, k) = v(j, k) v(j, k) = t Next k End Sub
Private Sub swapo(a As Object, b As Object) Dim t As Object
Set t = a Set a = b Set b = t End Sub '---- end VBA code ----
|I have a list of several hundred policies each with a different value. | Occasionally I have a request to 'sell off' policies to a certain value. At [quoted text clipped - 21 lines] | | Gerry GerryGerry - 11 Mar 2008 17:57 GMT Harlan's macro is a very powerful piece of code, but can it be adjusted to give the closest answer as if no exact match is found, it returns nothing which is of little use in my scenario. As to the solver addin solution, its far to slow to be of any practical use for lists over 25 items.
Does Harlan have a website by any chance which explains the code in any detail as I might have a bash at adjusting it my self
All help much appreciated.
> Hi Gerry, > [quoted text clipped - 28 lines] > | > | Gerry Niek Otten - 11 Mar 2008 20:18 GMT Hi Gerry,
I think you can set the tolerance with the constant Tol
 Signature Kind regards,
Niek Otten Microsoft MVP - Excel
| Harlan's macro is a very powerful piece of code, but can it be adjusted to | give the closest answer as if no exact match is found, it returns nothing [quoted text clipped - 38 lines] | > | | > | Gerry GerryGerry - 12 Mar 2008 12:15 GMT Thanks Niek that tolerance setting does the trick!
Regards
Gerry
> Hi Gerry, > [quoted text clipped - 319 lines] > | > | > | > | Gerry Dan O'Connell - 29 Apr 2008 21:01 GMT I tried the solver method and I get the following error message: Too Many Adjustable Cells - I am running MS Office 2007. Can't we do this through Solver and not have to run any lengthy VB code?
> Hi Gerry, > [quoted text clipped - 25 lines] > | > | Gerry Dana DeLouis - 11 Mar 2008 17:04 GMT > policies 3, 4, 5, 6 & 8 would be a better choice > as they total $54,302.54 As a side note, I show the closest to 54,500 as being...
604.5 + 53713.97 =
54,318.47
 Signature Dana DeLouis
> I have a list of several hundred policies each with a different value. > Occasionally I have a request to 'sell off' policies to a certain value. [quoted text clipped - 22 lines] > > Gerry Bernd P - 11 Mar 2008 18:21 GMT Hello Gerry,
I suggest to look here: http://michael-schwimmer.de/vba096.htm and then to download: http://michael-schwimmer.de/download/Ergebnissuche.zip
It is a nice beautification of Mr. Excel's winning solution of his challenge of the month (August 2002): http://www.mrexcel.com/pc09.shtml and it comes with a tolerance parameter.
It is quite self-explanatory but it is in German. I have an English translation if you need it.
Regards, Bernd
Niek Otten - 11 Mar 2008 20:21 GMT Hi Bernd,
That one is on my list (a nice compact one!) but not yet in my archive. I don't know why, but I had the idea there was some discussion about this solution. Do you know anything about that? I may be wrong. Unfortunately I don't have any application to use as a test case........
 Signature Kind regards,
Niek Otten Microsoft MVP - Excel
| Hello Gerry, | [quoted text clipped - 10 lines] | Regards, | Bernd Bernd P - 12 Mar 2008 11:02 GMT Hi Niek,
That VBA macro stops with the first fitting combination while Harlan's approach will return all possible solutions.
Of course we could adjust both approaches to behave like the other one...
Regards, Bernd
|
|
|