Preface: Messing around with cube math and after looking around at all the various options I seem to have run into an issue with my current structure. Maybe someone would love to point out what I am missing here?
Private Sub cube3()
Dim x(0 To 2, 0 To 2) As Single
Dim y(0 To 2, 0 To 2) As Single
Dim z(0 To 2, 0 To 2) As Single
For a = 0 To 2
For b = 0 To 2
Count = (Count + 3) / 2 * 1.5
x(a, b) = Count
y(a, b) = Count
z(a, b) = Application.WorksheetFunction.MMult(x, y)(a, b) '****** This is where the issue occurs
Debug.Print ; x(a, b)
Debug.Print ; z(a, b)
Next
Next
End Sub
In this instance MMULT() is not the way to go it is not made for single value multiplication.
It systematically does something similar to SUMPRODUCT(). As it multiplies each item in a row in the First array with each item in a column in the second array.
Becomes
See HERE for a much better explanation.
In this code you are multiplying single values so a simple multiplication would work:
= x(a,b)*y(a, b)
As far as my research has found to multiply each value in one array with its sister value in a second array, the only method is to loop.
You are already using the quickest method by which to do this. By using array instead of worksheet ranges even with larger datasets this will still be pretty quick.
If you want to use the MMULT() then fill both arrays first and declare z as a variant:
Private Sub cube3()
Dim x(0 To 2, 0 To 2) As Single
Dim y(0 To 2, 0 To 2) As Single
Dim z
For a = 0 To 2
For b = 0 To 2
Count = (Count + 3) / 2 * 1.5
x(a, b) = Count
y(a, b) = Count
Debug.Print x(a, b)
Debug.Print y(a, b)
Next
Next
z = Application.WorksheetFunction.MMult(x(), y()) '****** This is where the issue occurs
Range("A1").Resize(UBound(z, 1), UBound(z, 2)).Value = z
End Sub
Related
Im trying to fill multiple matrices according to inputs from a userform.
Right now i have to create 5 matrices but i want this to be able to change in the future, so i want to create the variables "on the fly" in my loop. I basically want to Create Matrix1, Matrix2, Matrix3 ... Matrixn, but i can't figure how to do it. Any Ideas ?
Following you can see the script --> The two last lines are totally wrong (i know it) but i just added them in hope that you understand what i want to do :)
PS: And most important i should be able to then look to Matrix1(i,j),..., Matrixn(i,j) values easily
Looking Forward your help <3
'FicheCalcul ----------------------------------------------------------------------------------------------------
Dim Temp4 As String
Dim Temp5 As String
Dim MatrixTemp(3 - 1, 5 - 1) As Double 'm-1,x-1
For i = 1 To 5 'n
For j = 0 To 3 - 1 'm-1
For k = 0 To 5 - 1 'X-1
Temp4 = j & "C" & i
Temp5 = Temp4 & k
If UserForm1.Controls(Temp5) = False Then
MatrixTemp(j, k) = ""
Else
If UserForm1.Controls(Temp5) = True Then
MatrixTemp(j, k) = k - 2 ' -2 -1 0 1 2
End If
End If
Next k
Next j
Dim ("Matrix"&i)(3-1,5-1) As Double
("Matrix"&i) = MatrixTemp
Next i
You cannot have variable variable names. Therefore you need to use arrays.
Option Explicit
Sub example()
Dim MatrixTemp(4, 5) As Double
'do your matrix stuff here
MatrixTemp(0, 0) = 1
MatrixTemp(0, 1) = 2
MatrixTemp(0, 2) = 3
Dim Matrix(4) As Variant 'create an array of 5 matices
Matrix(0) = MatrixTemp 'fill your temp matrix into the first matrix
Debug.Print Matrix(0)(0, 2) 'outputs 3
End Sub
Situation
I have a UDF that works with a range that it is passed that is of variable height and 2 columns wide. The first row will contain text in column 1 and an empty column2. The remainder of column 1 will contain unsorted text with an associated value in the same row in column 2. I need to sort the data such that if some text in column 1 also appears in some other text in column.
Problem
My VBA skills are all self taught and mimimal at best. I remember a few decades ago in university we did bubble sorts and played with pointers, but I no longer remember how we achieved any of that. I do well reading code but creating is another story.
Objective
I need to generate a sort procedure that will produce unique text towards the bottom of the list. I'll try wording this another way. If text in column1 can be found within other text in column, that the original text need to be placed below the other text it can be found in along with its associated data in column 2. The text is case sensitive. Its not an ascending or descending sort.
I am not sure if its a restriction of the UDF or not, but the list does not need to be written back to excel, it just needs to be available for use in my UDF.
What I have
Public Function myFunk(rng As Range) As Variant
Dim x As Integer
Dim Datarange As Variant
Dim Equation As String
Dim VariablesLength As Integer
Dim Variable As String
Datarange = rng.Value
'insert something around here to get the list "rng or Datarange" sorted
'maybe up or down a line of code depending on how its being done.
Equation = Datarange(1, 1)
For x = 2 To UBound(Datarange, 1)
VariablesLength = Len(Datarange(x, 1)) - 1
Variable = Left$(Datarange(x, 1), VariablesLength)
Equation = Replace$(Equation, Variable, Datarange(x, 2))
Next x
myFunk = rng.Worksheet.Evaluate(Equation)
End Function
Example Data
Any help with this would be much appreciated. In that last example I should point out that the "=" is not part of the sort. I have a routine that strips that off the end of the string.
So in order to achieve what I was looking for I added a SWAP procedure and changed my code to look like this.
Public Function MyFunk(rng As Range) As Variant
Dim x As Integer
Dim y As Integer
Dim z As Integer
Dim datarange As Variant
Dim Equation As String
Dim VariablesLength As Integer
Dim Variable As String
'convert the selected range into an array
datarange = rng.Value
'verify selected range is of right shape/size
If UBound(datarange, 1) < 3 Or UBound(datarange, 2) <> 2 Then
MyFunk = CVErr(xlErrNA)
Exit Function
End If
'strip the equal sign off the end if its there
For x = 2 To UBound(datarange, 1)
If Right$(datarange(x, 1), 1) = "=" Then
datarange(x, 1) = Left$(datarange(x, 1), Len(datarange(x, 1)) - 1)
End If
Next x
'sort the array so that a variable does not get substituted into another variable
'do a top down swap and repeat? Could have sorted by length apparently.
For x = 2 To UBound(datarange, 1) - 1
For y = x + 1 To UBound(datarange, 1)
If InStr(1, datarange(y, 1), datarange(x, 1)) <> 0 Then
For z = LBound(datarange, 2) To UBound(datarange, 2)
Call swap(datarange(y, z), datarange(x, z))
Next z
y = UBound(datarange, 1)
x = x - 1
End If
Next y
Next x
'Set the Equation
Equation = datarange(1, 1)
'Replace the variables in the equation with values
For x = 2 To UBound(datarange, 1)
Equation = Replace$(Equation, datarange(x, 1), datarange(x, 2))
Next x
'rest of function here
End Function
Public Sub swap(A As Variant, B As Variant)
Dim Temp As Variant
Temp = A
A = B
B = Temp
End Sub
I sorted by checking to see if text would substitute into other text in the list. Byron Wall made a good point that I could have sorted based on text length. Since I had completed this before I saw the suggestion it did not get implemented though I think it may have been a simpler approach.
I have a procedure which involves ranking values. My code (stripped down to important parts) looks like this:
Dim myArray() as variant
ReDim myArray(1 to 4, 1 to x)
for i = 1 to x
myArray(1,i) = a
myArray(2,i) = b
myArray(3,i) = c
next i
for j = 1 to x
myArray(4,j) = Application.Rank_Eq(myArray(3,j), Application.Index(myArray,3,0), 1)
next j
for k = 1 to x
myFunction(myArray(4,k))
next k
Debugging it, the for j = 1 to x loop works fine if I just return, say, the value of j or the value of myArray(3,j) but it breaks out of the loop at j=1 when I use the Application.Rank_Eq() formula.
Have I done something really stupid that I just can't see, or is this an Excel issue?
EDIT:
I tried using the following to debug:
myIndex = Application.Index(myArray,3,0)
for k = 1 to x
MsgBox myIndex(k,1)
a = Application.Rank_Eq(myIndex(1,k), editedRows, 1)
next k
This appears to run ok - i.e. each value of myIndex(k,1) is returned. However, if I add MsgBox a before next k, then it breaks. This suggests it's something to do with the value being returned by Rank_Eq, no?
Not sure it's part of the issue - but I had to access the Rank_Eq method through the WorksheetFunction object, not the Application object.
Secondly, you'll notice that this function needs a Double and a Range for the first 2 arguments. Currently you are supplying a Variant and whatever the value is from your Index() method.
Try casting the Variant to a Double like so for the first argument:
CDbl(myArray(3, j))
For the second argument, I have no idea from your question how the array gets populated so I can't guess where the Range argument needs to refer to...
I have a need to run successive passes of built in excel functions on a single matrix of input.
The problem is, the input [range] is what I assume, a pointer constant.
So sure, I can do a WorkSheetFunction calculations on the [range] input and place the output into a variant.
But, I do have a need to run more passes on the variant data. I have a more advanced calculation that is going to run 4 transforms on data that use standard excel functions like average, and median.
Here's my code
Public Function RankECDF(ByRef r_values As Range, Optional ByVal zeroFlag As Boolean = 0) As Variant()
Dim i As Integer, j As Integer, N As Integer, M As Integer
Dim total As Integer
Dim y() As Variant
N = r_values.Rows.Count
M = r_values.Columns.Count
y = r_values.Value 'copy values from sheet into an array
Dim V() As Variant
Dim AltV As Variant
Dim OutV As Variant
Dim OutAltV As Variant
'quite possible to makes the Variant larger to hold the "other arrays"
ReDim V(1 To N, 1 To M)
ReDim AltV(1 To N, 1 To M)
ReDim OutV(1 To N, 1 To M)
ReDim OutAltV(1 To N, 1 To M)
'first pass just checks for zero's. Could speed this process up by implementing the zeroFlag check to skip the double loop
total = WorksheetFunction.Sum(r_values)
For R = 1 To N
For C = 1 To M
If y(R, C) = "" Then
V(R, C) = ""
AltV(R, C) = 0
Else
'would error if cell was ""
'V(R, C) = WorksheetFunction.Average(WorksheetFunction.Rank(y(R, C), r_values, 1), WorksheetFunction.CountIf(r_values, "<=" & y(R, C))) / WorksheetFunction.Count(r_values)
V(R, C) = y(R, C)
AltV(R, C) = y(R, C)
End If
Next C
Next R
'second loop does rankecdf conversions
For RA = 1 To N
For CA = 1 To M
'OutV(RA, CA) = 1
'OutV(RA, CA) = WorksheetFunction.Rank(V(RA, CA), V, 1)
'OutAltV(RA, CA) = 2
'OutAltV(RA, CA) = WorksheetFunction.Average(WorksheetFunction.Rank(y(RA, CA), r_values, 1), WorksheetFunction.CountIf(r_values, "<=" & y(RA, CA))) / WorksheetFunction.Count(r_values)
Next CA
Next RA
If (zeroFlag) Then
RankECDF = AltV
'RankECDF = OutAltV(1 to N, 1 to M)
Else
RankECDF = V
'RankECDF = OutV(N, M)
End If
End Function
The problem can be identified right around here:
OutV(RA, CA) = WorksheetFunction.Rank(V(RA, CA), V, 1)
WorksheetFunction.Rank(y(R, C), r_values, 1)
You cannot put an Array on arg1. Just do:
i = y(R, C)
Then:
WorksheetFunction.Rank(i, r_values, 1)
It worked fine for me
Updated from comments as I see the answer I initially posited misread the problem:
As a general rule, arrays and performing calculations purely in memory are faster than you might think. For one example I used to use the Application.Match function to find the index position of a value in an array, rather than simple brute force iteration. Turns out that iteration was a far faster (up to 10x faster!!!) method. Check out Tim's answer to my question about Matching values in a string array.
I suspect it is the same with rank/sorting. Worksheet functions are expensive. For/Next is not, relatively speaking.
As for the specific needs to rank from an array, there are examples of custom functions which rank and sort arrays, collections, dictionaries, etc. I ultimately end up using a bunch of Chip Pearson's Array helper functions, he has a number of them; which do really cool sh!t like reversing an array, sorting array, determining whether an array is allocated (I use this one a lot) or empty, or all numeric, etc. There are about 30 of them.
here is the code to sort an array.
Note: I did not post his code because there is a lot of it. WHile it appears daunting, because it is a lot of code to re-invent the wheel, it does work and saves a lot of trouble and is very useful. I don't even use these in Excel, since I do most of my dev in PowerPoint now -- I think all of these modules ported over with zero or almost zero debugging on my end. They're really buttoned up quite nicely.
Getting the rank
Once the array is "sorted" then determining the rank of any value within it is trivial and only requires some tweaking since you may want to handle ties appropriately. One common way of dealing with ties is to "skip" the next value, so if there is a two-way tie for 2nd place, the rank would go {1, 2, 2, 4, 5, 6, etc.}
Function GetRank(arr As Variant, val As Variant)
'Assumes arr is already sorted ascending and is a one-dimensional array
Dim rank As Long, i As Long
Dim dictRank As Object
Set dictRank = CreateObject("Scripting.Dictionary")
rank = 0
For i = LBound(arr) To UBound(arr)
rank = rank + 1
If dictRank.Exists(arr(i)) Then
'Do nothing, handles ties
Else
'store the Key as your value, and the Value as the rank position:
dictRank(arr(i)) = rank
End If
If arr(i) = val Then Exit For
Next
GetRank = rank
End Function
I'm trying to teach myself some basic VBA in Excel 2010 and I've come across a problem I can't google myself out of. The objective is to create a button which when pressed, automatically does linest for me and writes the values into an array. So far, this is my code.
Private Sub CommandButton1_Click()
Dim linest As Variant
Dim linestArray(4,1) as Variant
Dim i As Integer, j as Integer
linest = Application.LinEst(Range("U49:U51"), Range("T49:T51"), True, True)
For i = 0 To 4
linestArray(i,0) = accessing values of linest variable fyrst column
Cells(68 + i, 21) = linestArray(i,0)
Next
For j = 0 To 4
linestArray(j,1) = accessing values of linest variable second column
Cells(68 + j, 22) = linestArray(j,0)
Next
End Sub
How do I access the values of variable linest so I can store them to an array and print them? Thank you.
EDIT: I figured it out. Variable linest is already an array! I feel pretty dumb. Sorry, this can be ignored.
New code:
Dim linestArray As Variant
linestArray = Application.LinEst(Range("U49:U51"), Range("T49:T51"), True, True)
For i = 0 To 4
For j = 0 To 1
Cells(68 + i, 21 + j) = linestArray(i + 1, j + 1)
Next
Next
The output of any such formula is a Variant array. So you've got that part right.
For a general approach to these Application. (use WorksheetFunction. instead, it's much faster) type functions is...
Type the function in Excel (as an array formula, Ctrl-Shift-Return, if need be)
The output is an N x M matrix of some sort (N =1 , M =1) for most cases
When you do Var = Application.Linest(xyx), the answer gets stored in Var
Get the size of the Var using Ubound(Var, 1), Ubound(Var, 2) to get number of rows and columns (note that these are base 0 type arrays)
Usually, the size will be one x one. In that case, your variable is stored in Var(0,0) i.e. a zero base multidimensional variant array, the top left element.
Hope this helps.