Error 9 using ReDim Preserve - arrays

This function returns a error 9, why? I already read couple of similar questions here but nothing really explained me well.
I'm really trying to understand why ReDim Preserve doesnt work in this case because from "j+1" onwards no input is made(i already checked my cells), so isnt overwriting nothing.
Heres the code
Function DadosAnoIndenizações()
ReDim dados(1 To 10000, 1 To 2)
j = 0
For i = 5 To 10000
If (IsNumeric(Worksheets(2).Cells(i, 8).Value) And Not IsEmpty(Worksheets(2).Cells(i, 8).Value)) Then
Ano = CInt(Right(Worksheets(2).Cells(i, 8).Value, 4))
SD = Worksheets(2).Cells(i, 11).Value
j = j + 1
dados(j, 1) = Ano
dados(j, 2) = SD
End If
Next i
ReDim Preserve dados(1 To j, 1 To 2)
DadosAnoIndenizações = dados
End Function
Thanks for any help guys

You are trying to change the first dimension of the array. Redim preserve can only change the last dimension of the array, which is why you're getting the error. You can find this information listed MSDN's website: ReDim Statement (Visual Basic), specifically the "Resizing with Preserve" section:
Resizing with Preserve. If you use Preserve, you can resize only the
last dimension of the array. For every other dimension, you must
specify the bound of the existing array.
For example, if your array has only one dimension, you can resize that
dimension and still preserve all the contents of the array, because
you are changing the last and only dimension. However, if your array
has two or more dimensions, you can change the size of only the last
dimension if you use Preserve.
Typically the way around that is to have the first dimension be your 1 to 2 and the second dimension be your 1 to j and then do a Application.Transpose when writing the results to the worksheet.

Related

Dynamic array not accepting to be redimensioned in 1x2 (2x2 and 2x1 works)

For work, I'm working with arrays in which I store prices of parts.
The idea is that I have an array with the different part numbers coded on several digits, I go in the excel workbook where all the parts and their prices are stored and search for these codes one by one.
Once a code found, I just do an "offset" to get the price in Euro and in Dollards, and fill another array with these prices.
My problem is that when the array with the parts numbers got only one part number, I don't achieve to "ReDim" the array to be a 1x2 array...
See the comments in the following code (Yes, I chose to start the arrays at 1 and not 0, it's way more convenient in this case)
Dim ToolPrices() As String
ReDim ToolPrices(1 To UBound(ToolTab()), 1 To UBound(ToolTab())) 'Normally, if "ToolTab()" is a 1x1 array, TooLPrices should be a 1x2 array... But no, it's a 1x1
[Later on in the code]
Tempo = ActiveCell.Offset(0, 2) 'gets the euro price
' the variable PositionPoint is the pointer for the "for" loop passing through all the arrays
If PositionPoint > UBound(ToolPrices(), 1) Then 'for avoiding to reduce the size of the Array and loosing data
ReDim Preserve ToolPrices(1 To 2, 1 To PositionPoint)
End If
ToolPrices(1, PositionPoint) = Tempo
Tempo = ActiveCell.Offset(0, 5) 'gets the dollard price
If PositionPoint > UBound(ToolPrices(), 2) Then 'for avoiding to reduce the size of the Array and loosing data
ReDim Preserve ToolPrices(1 To 2, 1 To PositionPoint)
End If
ToolPrices(2, PositionPoint) = Tempo 'In case of a 1x1 array with the parts numbers, this bugs as it is a 1x1 array in which I try to put something in the second line
I've been searching for a while without success...
I guess either the ReDim ToolPrices(1 To UBound(ToolTab()), 1 To UBound(ToolTab())) got a problem...
OR
the If PositionPoint > UBound(ToolPrices(), 2) Then
ReDim Preserve ToolPrices(1 To 2, 1 To PositionPoint)
End If
which isn't accessed as the UBound(ToolPrices(), 2) = 1 when I would expect a 0 or "Empty" or "" or error or anything except a 1 as the second dimension don't exist...
Thank's by advance for your help !
Ok, I think I got it, thank's to this :
https://www.excel-easy.com/vba/array.html
So, first you define the number of lines, then the number of columns up to 60 columns
I have to redo everything :'(

How to Pass an Array to and from a Function?

(Fair Warning, I am self taught on VBA so I apologize in advance for any cringe-worthy coding or notations.)
I have an estimating worksheet in excel. The worksheet will have a section for the user to input variables (which will be an array). The first input variable will "reset" the remaining input variables to a standard value when the first variable is changed. The standard values for the input variables are stored in a function in a module. I am attempting to fill the input variable array with the standard values from the function and then display those values on the sheet. I was easily able to do this without arrays but have had no luck in moving everything into arrays.
This is for excel 2010. I previously did not use arrays and created a new variable when needed, however the estimating sheet has grown much larger and it would be better to use arrays at this point. I have googled this question quite a bit, played around with removing and adding parenthesis, changing the type to Variant, trying to set the input variable array to be a variable that is an array (if that makes sense?), and briefly looked into ParamArray but that does not seem applicable here.
Dim BearingDim(1 To 9, 1 To 4, 1 To 8) As Range
Dim arrBearingGeneral(1 To 5, 1 To 8) As Range
Dim Test As Variant
Private Sub Worksheet_Change(ByVal Target As Range)
'Set General Variable array to cells on the worksheet
For i = 1 To 5
For j = 1 To 8
Set arrBearingGeneral(i, j) = Cells(9 + i, 3 + j)
Next j
Next i
'Set Bearing Input Variables to Cells on the Worksheet
For p = 1 To 4
For i = 1 To 9
Select Case p
Case Is = 1
Set BearingDim(i, p, 1) = Cells(16 + i, 4)
Case Is = 2
Set BearingDim(i, p, 1) = Cells(27 + i, 4)
Case Is = 3
Set BearingDim(i, p, 1) = Cells(37 + i, 4)
Case Is = 4
Set BearingDim(i, p, 1) = Cells(49 + i, 4)
End Select
Next i
Next p
'Autopopulate standard input variables based on Bearing Type
inputMD_StdRocker BearingType:=arrBearingGeneral(1, 1), _
arrBearingDim:=BearingDim
End Sub
Sub inputMD_StdRocker(ByVal BearingType As String, ByRef _
arrBearingDim() As Variant)
Dim arrBearingDim(1 To 9, 1 To 4)
Select Case BearingType
Case Is = "MF50-I"
For j = 1 To 2
arrBearingDim(2, j) = 20
arrBearingDim(3, j) = 9
arrBearingDim(4, j) = 1.75
Next j
arrBearingDim(5, 1) = 15
'There are numerous more select case, but those were removed to keep it
'short
End Select
End Sub
The expected output is my "BearingDim" Array will have certain array index values set to a standard value from the "inputMD_StdRocker" function. Then those values will be displayed in the cell that corresponds to the array index.
Currently, I get a compile error "Type Mismatch, Array or User-Defined Type Expected". I have been able to get around the type mismatch by removing the () from "arrBearingDim()" in the function title for "inputMD_StdRocker" however, it will not pass the values back to my "BearingDim" array.
Any help would be greatly appreciated.
This is a partial answer to what (I think) is a misunderstanding you have of how to use arrays. There are a few problems in your code.
First, you're defining a two-dimensional and a three-dimensional array of Ranges when I believe you really only want to store the values captured from the worksheet. (If I'm wrong, then you are never initializing the array of Ranges, so none of the ranges in the array actually point to anything.)
Secondly, it looks as if your initial array arrBearingGeneral is always filled from the same (static) area of the worksheet. If this is so (and you really do want the values from the cells, not an array of Range objects), then you can create a memory-based array (read this website, especially section 19). So the first part of your code can be reduced to
'--- create and populate a memory-based array
Dim bearingDataArea As Range
Dim arrBearingGeneral(1 To 5, 1 To 8) As Variant
Set bearingDataArea = ThisWorkbook.Sheets("Sheet1").Range("D10:K14")
arrBearingGeneral = bearingDataArea.Value
Optionally of course you can calculate the range of your data instead of hard-coding it ("D10:K14"), but this example follows your own example.
While this isn't a complete answer, hopefully it clears up an issue to get you farther down the road.

Excel transpose 2D array - Out of memory issue

I have a VBA macro, where I want to write out an array to an Excel sheet.
I'm getting an "Out of memory" runtime error on some machines. I can run it easily on my development PC, but my client has issues with it.
Here I define my Values array:
Dim Values()
Dim idx As Long
idx = 0
Then I have a for cycle where I dynamically redim the array, and add my values to it:
for cycle...
ReDim Preserve Values(16, 0 To idx)
Values(0, idx) = "some text"
Values(1, idx) = "some other text"
....
Values(15, idx) = "last values for this row"
idx = idx + 1
next
Then here is where my code fails:
With ws
.Range(.Cells(1, 1), .Cells(1+ idx - 1, 16)).value = TransP(Values)
End With
Here's the TransP transposing function:
Public Function TransP(var As Variant) As Variant
Dim outP() As Variant, i As Long, j As Long
ReDim outP(LBound(var, 2) To UBound(var, 2), LBound(var, 1) To UBound(var, 1))
For i = LBound(outP) To UBound(outP)
For j = LBound(var) To UBound(var)
outP(i, j) = var(j, i)
Next
Next
TransP = outP
End Function
As I said, I can run the macro, and get something like 108770 rows. The same 108770 rows don't work on my clients PC.
I expect that the TransP function gives up on his PC, so should I split up the array into multiple smaller chunks, and write them 1 by 1?
Or my data model is not good?
You could also create a loop to write your output array row by row, it will take more time but you will most likely don't get out of memory error.
In the past when I've got out of memory issues with an arrays I just tried to perform actions using regular excel commands, in this case you could just copy range and than paste transposed values:
.PasteSpecial Paste:=xlPasteValues, Transpose:=True
Your method of appending elements to Values is inefficient because every time an element is added a new array is created and the values of the existing one copied to it. During this time twice the memory is in use, and if a large array is copied in this way multiple times in quick succession only heaven can know what demands are put on the RAM management.
The better way is to dimension the array larger than will be required (once), count the number of elements written to it and use Redim Preserve to reduce its size (once) when you are done.
I suspect this might be due to you not specifying the dimension in your TransP function. You are also defining your loops with both arrays. You've sized them identically (although with the dimensions switched) - Just use the same one to define your For loop
Public Function TransP(var As Variant) As Variant
Dim outP() As Variant, i As Long, j As Long
ReDim outP(LBound(var, 2) To UBound(var, 2), LBound(var, 1) To UBound(var, 1))
For i = LBound(outP, 1) To UBound(outP, 1)
For j = LBound(outP, 2) To UBound(outP, 2)
outP(i, j) = var(j, i)
Next
Next
TransP = outP
End Function
I don't think it's the TransP function, since that is already handling everything in a loop. I experienced the same sort of issue and there this error occured when I tried to transfer a large multidimensional array to a range.
My solution was to create a loop and do about a 1000 rows each time, but that depends on the clients pc I guess with how many rows you can do.
To take into account that var will not end on a certain step size, you could do a while loop:
i = 0
with ws
Do while i < Ubound(var)
max = application.worksheetfunction.max(1000,Ubound(var) - i) 'At some point, you could have less than 1000 left
.range(.cells(i+1,1), .cells(i+max)) = TransP(var,i,max)
i = i + 1000
Loop
In TransP you now use the lower- and upperbound of var, but if you add two variables from the loop, you can use them to only take a piece of the array.

ReDim existing array with a second dimension?

I declared an array in my VBA function that has a dynamic size. As I cannot ReDim the first dimension of a two- or more-dimensional array, can I add a second dimension to a set array?
This is how I dynamically set the size of my array.
Dim nameArray() As String
Dim arrayCount As Long
For i = 1 To 100
ReDim Preserve nameArray(1 to arrayCount)
nameArray(arrayCount) = "Hello World"
arrayCount = arrayCount + 1
Next i
Now I would like to add a second dimension.
ReDim Preserve nameArray(1 To arrayCount, 1 To 5)
doesn't work.
Is there a workaround?
There isn't any built-in way to do this. Just create a new two-dimensional array and transfer the contents of your existing one-dimensional array into the first row of that new array.
This is what this function does:
Function AddDimension(arr() As String, newLBound As Long, NewUBound As Long) As String()
Dim i As Long
Dim arrOut() As String
ReDim arrOut(LBound(arr) To UBound(arr), newLBound To NewUBound)
For i = LBound(arr) To UBound(arr)
arrOut(i, newLBound) = arr(i)
Next i
AddDimension = arrOut
End Function
Example usage:
nameArray = AddDimension(nameArray, 1, 5)
There is one (also works to delete a dimension), but you will have to think in terms of worksheet dimensions...
use transposition
While I highly prefer the previous 'by hand' method from Jean-François Corbett's and I don't like to rely on Excel build-in function (especially this one!), I would just like to clarify another way for future readers coming here:
adding a dimension to a 1d line vector (a row) means transposing it in Excel
Here, nameArray(1 to arrayCount) is a row (index is a column number) and because of it, if you add a dimension it will become a column since 2d arrays are indexed as (row,column). So, you can just do this:
nameArray = Application.Worksheetfunction.Transpose(nameArray) 'transforms the array to nameArray(1 To arrayCount, 1 To 1), so then:
redim preserve nameArray(1 To arrayCount, 1 To 5)
without any other manipulation.
BUT beware of the very confusing Excel's Transpose function (at least it is the case for me!): the advantage here is that it automatically adds a dimension and redimensions the array for you.
It works as expected only because you are using a 'based 1' index '1d array'.
IF this is not the case, all indices will be shifted by 1, (that's how Transpose is build in to be coherent with cells and ranges). That is: if you start with
nameArray(0 to arrayCount)
you will end up with
nameArray(1 to arrayCount + 1, 1 to 5)
with precautions
While I am at it, it may be off-topic and their are many topics about it, but their are other traps in this Transpose function one should never be enough warned about (not to mention it can consume more time and resources than Jean-François Corbett's solution) :
• if you are dealing with '1d column' in an excel 2d array (a column), that is an array:
nameArray(1 to arrayCount, 1 to 1) [*]
and if you make the transpose of it, the column dimension will be "skipped" and the result will be:
nameArray(1 to arrayCount)
It makes sense since you will end up with an Excel row (so why bother with an extra dim?). But I have to say this is not the intuitive behaviour I would expect, which should be more something like nameArray(1 to 1, 1 to arrayCount).
Note that, a contrario, it can be used to delete a dimension and redimension the array automatically from 2d to 1d: redim preserve the last dimension to 1 to 1 and then transpose! This is the resulting array just above.
• But finally all is not lost: suppose you transpose this array of 1 line:
nameArray(0 to 0, 0 to arrayCount)
you correctly get an array of 1 column:
nameArray(1 to arrayCount + 1, 1 to 1)
(well, almost) - and look back at ref [*] now...
Thus, if you are to use this build-in function and if, moreover you need more dimensions or worst, need to compose transpositions, it can become a bit tricky...
For all this or if you simply need to know correct indexing and number of dimensions of your arrays (not only number of columns), I would suggest a very useful function from users John Coleman and Vegard, the post just below.
While all this should appear logical and trivial for people used to work with Excel sheets, it's very not the case when you are more used to matrix manipulations and I think suggesting the use of this Transpose function should come with some precisions.

How to prevent dynamic array from including a blank first element

I'm really struggling with why my array keeps including an empty element as its first element.
I'm populating an array from the selected choices in a listbox and I keep getting an empty first element in the array. I'm not getting any empty elements afterwards, just for the first element.
I've looked at lots of code to remove an empty element, all of which is beyond my understanding of arrays at this point, but that is not ideally for what I'm looking: I'm looking to prevent that empty element from appearing in my array in the first place.
Would anyone please help me understand what I need to change? I've tried using Option Base 1 and iterating from 1 instead of 0, but I get an error when trying to do so. Additionally, I did Redim my array as (0 To 0) and (0 to UBound) as well but nothing changed from my code below.
I'm assuming it has to do with the way I'm iterating through my listbox elements but I'm not sure how to fix the issue.
ReDim Part_Number_Array(1 To 1) As Variant
For Selected = 0 To Part_Number_Select.ListCount - 1
If Part_Number_Select.Selected(Selected) = True Then
ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) + 1) As Variant
'Add Part Number to the Array
Part_Number_Array(UBound(Part_Number_Array)) = _
Part_Number_Select.List(Selected)
'UpperBoundCount = UBound(Part_Number_Array)
'MsgBox "The upper bound is " & UpperBoundCount
End If
Next Selected
Thanks in advance for your help.
That is a known vb limitation which causes us all some "Empty/not empty" and member-count issues.
I have dealt with this by:
Define a class with Add/Delete/Find/etc methods no showing the array and provide a Count property. Also, you can use a Collection (and then there are some interesting derivatives, like the Dictionaries)
OR
OPTION BASE 0 (to be more C like) and use a _Count variable, initialized to 0, to keep track exactly how many useful items does the array hold and do Redims 0 to NewCount+1.
I think it is because you are redimming 1 to 1 outside the loop, then inside the loop you are redimming again before adding another element to it, then when you do add the element you are using the ubound. So it seems what happening is, you're redimming 1 to 1, which gives the array 1 empty slot. then, inside the loop you are redimming it 1 to UBound(Part_Number_Array) + 1). the + 1 at the end adds another slot (ubound is currently 1, 1 + 1 = 2) and then, when you add the element you are using Part_Number_Array(UBound(Part_Number_Array)) = so that statement puts the element at the ubound of the array, which is 2, leaving the first spot blank.
The solution:
ReDim Part_Number_Array(1 To 1) As Variant
For Selected = 0 To Part_Number_Select.ListCount - 1
If Part_Number_Select.Selected(Selected) = True Then
'Add Part Number to the Array before redimming, thus putting the new element in the 1 empty slot
Part_Number_Array(UBound(Part_Number_Array)) = _
Part_Number_Select.List(Selected)
'UpperBoundCount = UBound(Part_Number_Array)
'MsgBox "The upper bound is " & UpperBoundCount
ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) + 1) As Variant
End If
Next Selected
then, after the loop you can do - ReDim Preserve Part_Number_Array(1 To UBound(Part_Number_Array) - 1) notice the minus 1, this will eliminate the last slot (which is empty because of having a redim statement without adding another element)

Resources