I have a simple excel UDF for converting an array of mass values to mol fractions. Most times, the output will be a column array (n rows by 1 column).
How, from within the VBA environment, do I determine the dimensions of the target cells on the worksheet to ensure that it should be returned as n rows by 1 column versus n columns by 1 row?
Function molPct(chemsAndMassPctsRng As Range)
Dim chemsRng As Range
Dim massPctsRng As Range
Dim molarMasses()
Dim molPcts()
Set chemsRng = chemsAndMassPctsRng.Columns(1)
Set massPctsRng = chemsAndMassPctsRng.Columns(2)
chems = oneDimArrayZeroBasedFromRange(chemsRng)
massPcts = oneDimArrayZeroBasedFromRange(massPctsRng)
'oneDimArrayZeroBasedFromRange is a UDF to return a zero-based array from a range.
ReDim molarMasses(UBound(chems))
ReDim molPcts(UBound(chems))
totMolarMass = 0
For chemNo = LBound(chems) To UBound(chems)
molarMasses(chemNo) = massPcts(chemNo) / mw(chems(chemNo))
totMolarMass = totMolarMass + molarMasses(chemNo)
Next chemNo
For chemNo = LBound(chems) To UBound(chems)
molPcts(chemNo) = Round(molarMasses(chemNo) / totMolarMass, 2)
Next chemNo
molPct = Application.WorksheetFunction.Transpose(molPcts)
End Function
I understand that, if nothing else, I could have an input parameter to flag if return should be as a row array. I'm hoping to not go that route.
Here is a small example of a UDF() that:
accepts a variable number of input ranges
extracts the unique values in those ranges
creates a suitable output array (column,row, or block)
dumps the unique values to the area
Public Function ExtractUniques(ParamArray Rng()) As Variant
Dim i As Long, r As Range, c As Collection, OutPut
Dim rr As Range, k As Long, j As Long
Set c = New Collection
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' First grab all the data and make a Collection of uniques
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next
For i = LBound(Rng) To UBound(Rng)
Set r = Rng(i)
For Each rr In r
c.Add rr.Value, CStr(rr.Value)
Next rr
Next i
On Error GoTo 0
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' next create an output array the same size and shape
' as the worksheet output area
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
k = 1
With Application.Caller
ReDim OutPut(1 To .Rows.Count, 1 To .Columns.Count)
End With
For i = LBound(OutPut, 1) To UBound(OutPut, 1)
For j = LBound(OutPut, 2) To UBound(OutPut, 2)
If k < c.Count + 1 Then
OutPut(i, j) = c.Item(k)
k = k + 1
Else
OutPut(i, j) = ""
End If
Next j
Next i
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' put the data on the sheet
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
ExtractUniques = OutPut
End Function
You should return two dimensional arrays: n × 1 for row and 1 × n for column vectors.
So you need either
Redim molPcts(1, Ubound(chems) + 1)
or
Redim molPcts(Ubound(chems) + 1, 1)
To refer to them, you need to use both indices:
molPcts(1, chemNo + 1)
or
molPcts(chemNo + 1, 1)
If you prefer 0-based arrays, the redim should be like this:
Redim molPcts(0 To 0, 0 To Ubound(chems))
Redim molPcts(0 To Ubound(chems), 0 To 0)
Related
I am trying to create a function that finds the mean of the last d days from an array. My array is a time series with dates as col1 and prices as col2.
I want my function to be to allow the user to select the range, enter the number of days in past he wants the mean, and a Boolean whether the data is ascending or descending. if the number of elements in the series doesn't match d, example mean of 32 + "" then the function returns 0.
the Problem i am having is when i want to use the drag down in excel to fill the rest of the columns, the function doesn't work. for example for the sorted array; it takes mean of 56 + 34, then using drag down in excel the second cell should be the mean of 34 + 22 except it returns 0 and so on..
Function meanby(x As Range, d As Integer, sortarr As Boolean) As Double
Dim arr() As Variant
Dim i As Integer
Dim j As Integer
Dim count As Integer
Dim total As Double
Dim n As Integer
Dim temp As Variant
Dim arr2 As Variant
arr = rgntoarr(x)
n = x.Rows.count
If sortarr = False Then
For i = 1 To n / 2
temp = arr(i, 2)
arr(i, 2) = arr(n - i + 1, 2)
arr(n - i + 1, 2) = temp
Next i
End If
arr2 = arr
For j = 1 To d
total = total + arr2(j, 2)
If arr2(j, 2) = "" Then
Exit For
End If
i = i + 1
count = count + 1
Next j
If count < d Then
meanby = 0
Else
meanby = total / count
End If
End Function
I have a array with 8 columns and 42 rows B2 TO I43. I have to compare this array with other arrays in the same sheet so that every array have same values. I defined array1 Rang('B2;I43") and need to compare every other array of same size. how to that in VBA.
My code is
Sub driver()
Dim array1, array2, m, n
Set array1 = Range("B2,I43")
total_rows = 42
total_cols = 8
Set array2 = Range("B44:I85")
For i = 1 To total_rows
For j = 1 To total_cols
If array1(i, j) = array2(i, j) Then
array2.Cells(i, j).Interior.ColorIndex = 0
ElseIf array1(i, j) <> array2(i, j) Then
array2.Cells(i, j).Interior.ColorIndex = 3
End If
Next j
Next i
End Sub
I want array2 to point to other set of values. Every array start after 42 rows.
Have you tried to use Conditional formatting instead? Your suggested VBA code can easily be solved with conditional formatting by comparing each cell in array2 with the same cell in array1 and use colours to mark if the cells are equal or not
Edit
I have modified your code. Instead of using two ranges I have used an "row offset" for each array you have on your sheet. It then compares the cells from your source array (array1 in your code) with the cells that are found next_array_offset rows down. When the comparison has been made, the offset is increased with 42. The loop ends when there are no more values to be found.
Is this what you was looking for?
Sub driver()
Dim r As Integer
Dim c As Integer
Dim source_row As Integer
Dim source_col As Integer
Dim total_rows As Integer
Dim total_cols As Integer
Dim next_array_offset As Integer
source_row = 2 ' row B
source_col = 2 ' col 2
total_rows = 42
total_cols = 8
next_array_offset = 42 ' distance in rows to next array
Do Until IsEmpty(Cells(source_row + next_array_offset, source_col).Value)
For r = 0 To total_rows - 1
For c = 0 To total_cols - 1
If Cells(source_row + r, source_col + c) = Cells(source_row + next_array_offset + r, source_col + c) Then
Cells(source_row + next_array_offset + r, source_col + c).Interior.ColorIndex = 0
Else
Cells(source_row + next_array_offset + r, source_col + c).Interior.ColorIndex = 3
End If
Next
Next
next_array_offset = next_array_offset + 42
Loop
End Sub
Your main task is to define the ranges accurately. In the code below I've assumed it's every 42 rows until end of data. You simply iterate your 42-row test arrays and compare against the reference array. To do this you basically need two row variables: one for your test array and one for your reference array.
The quickest way would be to read the test data just once into one big array and to create two ranges (one with matches and one with mis-matches) and then colour them at the end of the routine.
I don't know your colour palette (and therefore the color indexes) so I've used the .Color property. You can adjust this to suit.
Const ROW_COUNT As Long = 42
Const COL_COUNT As Long = 8
Const START_ROW As Long = 2
Dim refArray As Variant, testArray As Variant
Dim rowSize As Long, r As Long, c As Long, i As Long
Dim cell As Range, yesRng As Range, noRng As Range
'Read data into arrays
With Sheet1
'Find last row of data
rowSize = .Cells(.Rows.Count, "B").End(xlUp).Row
'Adjust last row to be multiple of 42
rowSize = Int((rowSize - START_ROW) / ROW_COUNT) * ROW_COUNT
refArray = .Cells(START_ROW, "B").Resize(ROW_COUNT, COL_COUNT).Value2
testArray = .Cells(START_ROW + ROW_COUNT, "B").Resize(rowSize, COL_COUNT).Value2
End With
'Compare test array with reference array
i = 1 'refArray row index
For r = 1 To UBound(testArray, 1)
For c = 1 To UBound(testArray, 2)
Set cell = Sheet1.Cells(r + START_ROW + ROW_COUNT - 1, c + 1)
If testArray(r, c) = refArray(i, c) Then
'It's a match so add to yes range
If yesRng Is Nothing Then
Set yesRng = cell
Else
Set yesRng = Union(yesRng, cell)
End If
Else
'It's a miss so add to no range
If noRng Is Nothing Then
Set noRng = cell
Else
Set noRng = Union(noRng, cell)
End If
End If
Next
'Increment ref row index or set back to 1 if at 42
i = IIf(i < ROW_COUNT, i + 1, 1)
Next
'Colour the ranges
If Not yesRng Is Nothing Then yesRng.Interior.Color = vbGreen
If Not noRng Is Nothing Then noRng.Interior.Color = vbRed
I have a array which have 1 or more columns and now I want to add one more column (consists only of 1), but I don't know how do do that. The situation looks like that:
My code:
Dim X() As Variant
X = Range("A1:C3").Value2
It's is important to put column with 1 as first. Probably I need to use ReDim Preserve but nothing works for me.
I think you have some options, but instead of extending the index of the array and transposing, trying to move the values etc which seems too much of a hassle, I would rather add 1 to the Excel range and then create the array:
Range("B1:D3").Value2 = Range("A1:C3").Value2
Range("A1:A3").Value2 = 1
X = Range("A1:D3").Value2
Resize the Array adding a column to the last dimension
Shift all the data to the right.
Assign 1 to the first position in each row
Sub AddColumnShiftData()
Dim X As Variant
Dim i As Long, j As Long
X = Range("A1:C3").Value2
ReDim Preserve X(1 To 3, 1 To 4)
For i = 1 To UBound(X)
For j = UBound(X, 2) To 2 Step -1
X(i, j) = X(i, j - 1)
Next
X(i, 1) = 1
Next
End Sub
Try matrix multiplication by the identify matrix....Well almost identity matrix. Then add 1 to every element in of the resulting matrix. You can use the Excel's Worksheet function for matrix multiplication.
Almost identity matrix
Dim X As Variant
X = Range("A1:C3").Value2
Dim Y As Variant
n = UBound(X, 2)
m = n + 1
Z = UBound(X, 1)
ReDim Y(1 To n, 1 To m)
'Set All values to zero
For i = 1 To n
For j = 1 To m
Y(i, j) = 0
Next j
Next i
' Set offset diagonal to 1
For i = 1 To n
Y(i, i + 1) = 1
Next i
' Matrix MMult
X = Application.WorksheetFunction.MMult(X, Y)
' Add 1 to the first column
For i = 1 To Z
X(i, 1) = 1
Next i
Alternative via Application.Index()
Just for fun (note that the resulting array is a 1-based 2-dim array):
Sub AddFirstIndexColumn()
Const FIXEDVALUE = 1 ' value to replace in new column 1
'[1] get data
Dim v: v = getExampleData()
'[2] define column array inserting first column (0 or 1) and preserving old values (1,2,3)
v = Application.Index(v, _
Application.Evaluate("row(1:" & UBound(v) & ")"), _
Array(1, 1, 2, 3)) ' columns array where 0 reinserts the first column
' [3] add an current number in the first column
Dim i As Long
For i = LBound(v) To UBound(v): v(i, 1) = FIXEDVALUE: Next i
End Sub
Function getExampleData()
' Method: just for fun a rather unusual way to create a 2-dim array
' Caveat: time-consuming for greater data sets; better to assign a range to a datafield array
Dim v
v = Array(Array(2, 3, 5), Array(3, 8, 9), Array(4, 2, 1))
v = Application.Index(v, 0, 0)
getExampleData = v
End Function
Related links
Some pecularities of `Application.Index()
Insert vertical slices into array
There has to be a simpler way to do this. I took the advice of one poster on this forum who said that I have to set my multidimensional array to a high number then redimension it to a lower number. But in order to get it to the right number I have to run it through two loops where it seems like there has to be a simpler way to do things. So I have the array ancestors which has several blanks in it which I'm trying to get rid of. The second dimension will always be 2. I first run it through a loop to determine the ubound of it. And I call that ancestors3. Then I run the ancestors3 array through a loop and populate the ancestors2 array.
For s = 1 To UBound(ancestors, 1)
temp_ancest = ancestors(s, 1)
If temp_ancest <> "" Then
uu = uu + 1
ReDim Preserve ancestors3(uu)
ancestors3(uu) = temp_ancest
End If
Next
Dim ancestors2()
ReDim ancestors2(UBound(ancestors3), 2)
For s = 1 To UBound(ancestors3, 1)
temp_ancest = ancestors(s, 1)
temp_ancest2 = ancestors(s, 2)
If temp_ancest <> "" Then
y = y + 1
ancestors2(y, 1) = temp_ancest
ancestors2(y, 2) = temp_ancest2
End If
Next
Reading your question i think you want this:
you have a 2D array ancestors that may have some blank entries in the 1st dimension
you want a copy of ancestors without those blank rows, called ancestors2
Here is one way to do this. See inline comments for explanation
Sub Demo()
Dim ancestors As Variant
Dim ancestors2 As Variant
Dim i As Long, j As Long
Dim LB as long
' Populate ancestors as you see fit
'...
' crate array ancestors2, same size as ancestors, but with dimensions flipped
' so we can redim it later
ReDim ancestors2(LBound(ancestors, 2) To UBound(ancestors, 2), _
LBound(ancestors, 1) To UBound(ancestors, 1))
' Loop ancestors array, copy non-blank items to ancestors2
j = LBound(ancestors, 1)
LB = LBound(ancestors, 1)
For i = LBound(ancestors, 1) To UBound(ancestors, 1)
If ancestors(i, 1) <> vbNullString Then
ancestors2(LB, j) = ancestors(i, LB)
ancestors2(LB + 1, j) = ancestors(i, LB + 1)
j = j + 1
End If
Next
' Redim ancestors2 to match number of copied items
ReDim Preserve ancestors2(LBound(ancestors2, 1) To UBound(ancestors2, 1), _
LBound(ancestors2, 2) To j - 1)
' Transpose ancestors2 to restore flipped dimensions
ancestors2 = Application.Transpose(ancestors2)
End Sub
I am trying to copy a range of cells from a range of rows from two workbooks. This information is used to do a comparison of the contents of both workbooks rows by ID.
The first solution I tried involved cell by cell "binary" comparison. This works for worksheets with few rows:
For i = 2 To LastSheetRow
Set FoundCell = Workbooks(WorkbookA).Sheets(SheetNameFromArray).Range("A:A").Find(What:=Workbooks(WorkbookB).Sheets(SheetNameFromArray).Cells(i, 1).Value)
If Not FoundCell Is Nothing Then
aCellValues(0) = 1
Workbooks(UserWorkbook).Sheets(SheetNameFromArray).Cells(i, LastSheetColumn + 1).Value = FoundCell.Row
For j = 2 To LastSheetColumn
Select Case Workbooks(WorkbookB).Sheets(SheetNameFromArray).Cells(i, j).Value
Case Is = Workbooks(WorkbookA).Sheets(SheetNameFromArray).Cells(FoundCell.Row, j).Value
aCellValues(j - 1) = 1
Case Else
aCellValues(j - 1) = 0
End Select
Next j
Else
End If
Next i
I would like to store the contents of one row of each of the two workbooks on one array to do the comparison, as I believe it's a faster solution.
After defining the range to do the comparison I encountered the following error when copying the cells into an array:
Subindex out of interval (Error 9)
This generates the error:
Dim aWorkbookBInfo() As Variant, aWorkbookAInfo() As Variant, rngWorkbookBToCompare As Range, rngWorkbookAToCompare As Range
Dim SumToCheck As Integer, FoundCell As Range, aCellValues() As Integer
ReDim aCellValues(LastSheetColumn - 1)
ReDim aWorkbookBInfo(LastSheetColumn - 1)
ReDim aWorkbookAInfo(LastSheetColumn - 1)
For i = 2 To LastSheetRow
Set FoundCell = Workbooks(WorkbookA).Sheets(SheetNameFromArray).Range("A:A").Find(What:=Workbooks(WorkbookB).Sheets(SheetNameFromArray).Cells(i, 1).Value)
If Not FoundCell Is Nothing Then
aCellValues(0) = 1
Workbooks(WorkbookB).Sheets(SheetNameFromArray).Cells(i, LastSheetColumn + 1).Value = FoundCell.Row
With Workbooks(WorkbookB).Sheets(SheetNameFromArray)
Set rngWorkbookBToCompare = Range(Cells(i, 2), Cells(i, LastSheetColumn))
End With
With Workbooks(WorkbookA).Sheets(SheetNameFromArray)
Set rngWorkbookAToCompare = Range(Cells(FoundCell.Row, 2), Cells(FoundCell.Row, LastSheetColumn))
End With
aWorkbookBInfo = rngWorkbookBToCompare
aWorkbookAInfo = rngWorkbookAToCompare
For j = 1 To LastSheetColumn - 1
If aWorkbookBInfo(j).Value = aWorkbookAInfo(j).Value Then
aCellValues(j) = 1
Else
aCellValues(j) = 0
End If
Next j
Else
End If
Next i
Complete Revision:
The range array assignment produces a two-dimensional array in these lines:
aWorkbookBInfo = rngWorkbookBToCompare
aWorkbookAInfo = rngWorkbookAToCompare
This happens regardless of how you defined and dimensioned them at the beginning of your code. Since they are a two-dimensional array, they must be addressed as aWorkbookBInfo(a, b) where a is a row and b is a column.
Unlike Ranges, where it is okay to reference the first cell in any range, you must fully address an array before attempting to reference the array item. So, while rngWorkbookBToCompare(j).Value works, aWorkbookBInfo(j).Value does not. Furthermore, Value is not necessarily a property of whatever object Excel puts in the array. If you want the first cell of column j, try adding the row and leaving off the reference to the Value property as in: aWorkbookBInfo(1, j).