Find the indices for the minimum values in a multi dimensional array in VBA - vba

In the code below I have an n x n x n array of values. I need to identify the indices that contain the minimum, second to minimum, third to minimum, ..., and put them into their own array to be used later on in the code. CC is currently defined as a 11 x 11 x 11 array and I need to identify the minimums. Below is the setup of my array CC that contains the values. n is defined as the length of the array h2s, which happens to be 11 in this case. h2st is the sum of the values in h2s.
h2s = [1.099, 0.988, 0.7, 0.8, 0.5, 0.432, 0.8, 1.12, 0.93, 0.77, 0.658]
h2st = 0
n = Ubound(h2s) - Lbound(h2s) + 1
For i = 1 to n
h2st = h2st + h2s(i)
Next i
For i = 1 To n
For j = i + 1 To n
For k = j + 1 To n
CC(i, j, k) = Abs(h2st - ((h2s(i) + h2s(j) + h2s(k)) * (n / 3)))
Next k
Next j
Next i

You can use this function that takes a multidimensional array and returns an array of its n minimum values, where n is a parameter. Importantly, the elements in the returned array are a data structure of Type Point, containing the coordinates and the value of each found point.
You can easily adjust it for finding the n max values, just by changing two characters in the code, as indicated in comments (the initialization and the comparison)
Option Explicit
Type Point
X As Long
Y As Long
Z As Long
value As Double
End Type
Function minVals(ar() As Double, nVals As Long) As Point()
Dim i As Long, j As Long, k As Long, m As Long, n As Long, pt As Point
'Initialize returned array with max values.
pt.value = 9999999# ' <-------- change to -9999999# for finding max
ReDim ret(1 To nVals) As Point
For i = LBound(ret) To UBound(ret): ret(i) = pt: Next
For i = LBound(ar, 1) To UBound(ar, 1)
For j = LBound(ar, 2) To UBound(ar, 2)
For k = LBound(ar, 3) To UBound(ar, 3)
' Find first element greater than this value in the return array
For m = LBound(ret) To UBound(ret)
If ar(i, j, k) < ret(m).value Then ' <------- change to > for finding max
' shift the elements on this position and insert the current value
For n = UBound(ret) To m + 1 Step -1: ret(n) = ret(n - 1): Next n
pt.X = i: pt.Y = j: pt.Z = k: pt.value = ar(i, j, k)
ret(m) = pt
Exit For
End If
Next m
Next k
Next j
Next i
minVals = ret
End Function
Sub Test()
Dim i As Long, j As Long, k As Long, pt As Point
Const n As Long = 11
ReDim CC(1 To n, 1 To n, 1 To n) As Double
For i = 1 To n
For j = 1 To n
For k = 1 To n
CC(i, j, k) = Application.RandBetween(100, 100000)
Next k
Next j
Next i
' Testing the function: get the smalles 5 values and their coordinates
Dim mins() As Point: mins = minVals(CC, 5)
' Printing the results
For i = LBound(mins) To UBound(mins)
Debug.Print mins(i).value, mins(i).X, mins(i).Y, mins(i).Z
Next
End Sub

Related

VBA Function where double loop only provides one output

I am new with VBA and trying to do a trinomial option pricing tree with parameters from -n to n defined. How can I rewrite my VBA function with a double loop to provide all output as it seems to only show the value of S. In the end I want to sum all values and discount them but I cannot get past my double loop providing only one output. If I put the MsgBox between k and i loop it returns nothing. Please can someone assist getting this to work as I don't have the hang of VBA yet.
Public Function LoopTest(S As Double, u As Double, n As Integer)
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim St() As Double
ReDim St(2 * n + 1)
For k = 0 To n Step 1
For i = -k To k Step 1
St(i, k) = S * u ^ i
Debug.Print St(i, k)
MsgBox ("DebugToPrint")
' Payoff(i,k) = Application.WorksheetFunction.Max((St(i,k) - 20), 0)
Next i
Next k
' For i from n to 2 step -1
' For j from 1 to n - 2
' Payoff(i - 1, j) = Payoff(i, j) + Payoff(i, j + 1) + Payoff(i, j + 2)
' Next i
' Next k
LoopTest = St()
End Function
How do I get output
k=0 St = S*u^0
k=1 St = S*u^-1 , St = S*u^0 , St = S*u^1
k=2 St = S*u^-2 , St = S*u^-1 , St = S*u^0 , St = S*u^1 , St = S*u^2
etc and then sum everything afterwards as output?
I'm not totally following the example but from the description it sounds like you're interested in a recursive process. It can be tricky and will leave you in an endless loop if you're not careful. Does this previous post help you? VBA recursive "For loops" Permutation?

VBA sorting a collection by value

I have the VBA collection below
I want to sort by the value such that the collection will end up with the highest double value in the highest index position (i.e. "e" with value 14 is in first index, "c" with value 10 is second, etc). How is this possible?
Public Function SortCollection(ByVal c As Collection) As Collection
Dim n As Long: n = c.Count
If n = 0 Then
Set SortCollection = New Collection
Exit Function
ReDim Index(0 To n - 1) As Long ' allocate index array
Dim i As Long, m As Long
For i = 0 To n - 1: Index(i) = i + 1: Next ' fill index array
For i = n \ 2 - 1 To 0 Step -1 ' generate ordered heap
Heapify c, Index, i, n
Next
For m = n To 2 Step -1 ' sort the index array
Exchange Index, 0, m - 1 ' move highest element to top
Heapify c, Index, 0, m - 1
Next
Dim c2 As New Collection
For i = 0 To n - 1 ' fill output collection
c2.Add c.item(Index(i))
Next
Set SortCollection = c2
End Function
Private Sub Heapify(ByVal c As Collection, Index() As Long, ByVal i1 As Long, ByVal n As Long)
' Heap order rule: a[i] >= a[2*i+1] and a[i] >= a[2*i+2]
Dim nDiv2 As Long: nDiv2 = n \ 2
Dim i As Long: i = i1
Do While i < nDiv2
Dim k As Long: k = 2 * i + 1
If k + 1 < n Then
If c.item(Index(k)) < c.item(Index(k + 1)) Then k = k + 1
End If
If c.item(Index(i)) >= c.item(Index(k)) Then Exit Do
Exchange Index, i, k
i = k
Loop
End Sub
Private Sub Exchange(Index() As Long, ByVal i As Long, ByVal j As Long)
Dim Temp As Long: Temp = Index(i)
Index(i) = Index(j)
Index(j) = Temp
End Sub
As per Domenic in comment and he hasn't answered.
"If you use a Dictionary object instead of a Collection and you can sort by value as shown here. – Domenic Aug 30 at 22:29 "
This works now.

Stirling numbers second kind in vba

i created a code in VBA to calculate the amount of combinations for stirling numbers of second kind. But in following example only half of the values are correct.
The result should be 1,7,6,1 if n is equal to 4. (Wikipedia stirling numbers
I get 1,7,6.5,4.16
Sub stirlingerzahlen()
Dim n As Integer
Dim sum As Double
Dim subsum As Double
Dim k As Long
Dim j As Long
n = 4
For k = 1 To n Step 1
For j = 0 To k Step 1
subsum = 1 / Application.WorksheetFunction.Fact(k) * (-1) ^ (k - j) * Application.WorksheetFunction.Fact(k) / Application.WorksheetFunction.Fact(j) * j ^ n
sum = sum + subsum
Next
Sheets("Tabelle2").Cells(k, 1) = sum
sum = 0
Next
End Sub
Can someone find the mistake?
There is another version of the formula which seems to be easier to implement:
http://home.mathematik.uni-freiburg.de/junker/ss10/DAS-SS10.pdf
(Page 13)
And here the updated code:
Sub stirlingerzahlen()
Dim n As Integer
Dim sum As Double
Dim subsum As Double
Dim k As Long
Dim j As Long
n = 4
For k = 1 To n Step 1
For j = 0 To k
subsum = (((-1) ^ (k - j)) * ((j ^ n) / (Application.WorksheetFunction.Fact(j) * Application.WorksheetFunction.Fact(k - j))))
sum = sum + subsum
Next
Sheets("Tabelle2").Cells(k, 1) = sum
sum = 0
Next
End Sub

run time error 5 in VBA excel when working with array

I use vba on excel 2007, OS: windows vista, to make calculation using kinematic wave equation in finite difference scheme. But, when it runs the run-time 5 (invalid procedure call or arguments) message appears. I really don't what is going wrong. Anyone can help?
Sub kwave()
Dim u(500, 500), yy(500, 500), alpha, dt, dx, m, n, so, r, f, X, L, K As Single
Dim i, j As Integer
dx = 0.1
dt = 0.01
L = 10
m = 5 / 3
r = 1
f = 0.5
n = 0.025
so = 0.1 'this is slope
alpha = 1 / n * so ^ 0.5
X = 0
For i = 0 To 100
Cells(i + 1, 1) = X
u(i, 1) = L - so * X
X = X + dx
Cells(i + 1, 2) = u(i, 1)
Next i
For j = 0 To 100
For i = 1 To 100
'predictor step
u(i, j + 1) = u(i, j) - alpha * dt / dx * (u(i + 1, j) ^ m - u(i, j) ^ m) + (r - f) * dt
'corrector step
K = u(i, j + 1) ^ m - u(i - 1, j + 1) ^ m '<<<<----- RUNTIME ERROR 5 HAPPENS AT THIS LINE
yy(i, j + 1) = 0.5 * ((yy(i, j) + u(i, j + 1)) - alpha * dt / dx * K + (r - f) * dt)
Next i
Next j
End Sub
You are declaring the variables wrong- the array should store a double/single but it is defaulting to a variant. See this article.
http://www.cpearson.com/excel/declaringvariables.aspx -
"Pay Attention To Variables Declared With One Dim Statement
VBA allows declaring more than one variable with a single Dim
statement. I don't like this for stylistic reasons, but others do
prefer it. However, it is important to remember how variables will be
typed. Consider the following code:
Dim J, K, L As Long You may think that all three variables are
declared as Long types. This is not the case. Only L is typed as a
Long. The variables J and K are typed as Variant. This declaration is
functionally equivalent to the following:
Dim J As Variant, K As Variant, L As Long You should use the As Type
modifier for each variable declared with the Dim statement:
Dim J As Long, K As Long, L As Long "
Additionally, when i = 99 and j = 10, u(99,11), which is j+1, produces a negative number. Note that this does not fully cause the problem though, because you can raise negative numbers to exponents. Ex, -5^3 = -125

Simulation runs fine # 10k cycles, but gets error 13 (type mismatch) # 100k cycles

First off, here's my code:
Sub SimulatePortfolio()
Dim lambda As Double
Dim num As Integer
Dim cycles As Long
Column = 12
q = 1.5
lambda = 0.05
cycles = 100000
Dim data(1 To 100000, 1 To 10) As Integer
Dim values(1 To 10) As Double
For i = 1 To 10
values(i) = 0
Next i
temp = lambda
For i = 1 To cycles
lambda = temp
num = 10
t = 0
Dim temps(1 To 10) As Integer
For k = 1 To 10
temps(k) = 1000
Next k
Do While (t < 10 And num > 0)
t = t + tsim(lambda, num)
For j = 1 To 10
If (j > t) Then
temps(j) = temps(j) - 50
End If
Next j
num = num - 1
If (num <= 0) Then
Exit Do
End If
lambda = lambda * q
Loop
For l = 1 To 10
values(l) = values(l) + temps(l)
data(i, l) = temps(l)
Next l
Next i
For i = 1 To 10
Cells(i + 1, Column) = values(i) / cycles
'Problem occurs on this line:
Cells(i + 1, Column + 1).Value = Application.WorksheetFunction.Var(Application.WorksheetFunction.Index(data, i, 0))
Next i
End Sub
Function tsim(lambda As Double, num As Integer) As Double
Dim v As Double
Dim min As Double
Randomize
min = (-1 / lambda) * Log(Rnd)
For i = 1 To (num - 1)
Randomize
v = (-1 / lambda) * Log(Rnd)
If (min > v) Then
min = v
End If
Next i
tsim = min
End Function
When I set the value for cycles to 10000, it runs fine without a hitch. When I go to 100000 cycles, it gets an Error 13 at the indicated line of code.
Having been aware that Application.Tranpose is limited to 65536 rows with variants (throwing the same error) I tested the same issue with Index
It appears that Application.WorksheetFunction.Index also has a limit of 65536 rows when working with variants - but standard ranges are fine
So you will need to either need to dump data to a range and work on the range with Index, or work with two arrays
Sub Test()
Dim Y
Dim Z
'works in xl07/10
Debug.Print Application.WorksheetFunction.Index(Range("A1:A100000"), 1, 1)
Y = Range("A1:A65536")
`works
Debug.Print Application.WorksheetFunction.Index(Y, 1, 1)
'fails in xl07/10
Z = Range("A1:A65537")
Debug.Print Application.WorksheetFunction.Index(Z, 1, 1)
End Sub