VBA prevent empty cells/data in sheet or array - arrays

I'm having some trouble to wrap my head around an array problem.
I collect data from a sheet and stores it into an array. xData(0) will be completed and can not miss data. But xData(1) there the values are stored may miss a value so I want to replace the empty cell with a 0. Instead of using for loops to add 0 into the sheets I'm thinking of using an array, due to the reason that I cant find a way to add 0 directly to the sheet without decreasing the speed. I have around 10 sheets in 15 different workbooks and therefor I need to find a faster way to do this.
My code snippet is as following.
Dim xData(1) As Variant
'Collect the data from the sheets ()
xData(0) = xData(1) = Application.Transpose(Range(Cells(1, 1), Cells(1, 1).End(xlDown)).Value2)
xData(1) = xData(1) = Application.Transpose(Range(Cells(1, 2), Cells(1, 2).End(xlDown)).Value2)
This will produce a 2D array but the xData(1) will not be the same size as xData(0) if any cell is empty (missing data). Is there a way to change the size of xData(1) to fit the size of xData(0), and then add 0 to the remaining length. Is there any other way because this may cause problems later on too because of the case if 1 value is missing in the middle.

If your goal is to replace empties with zeros, here is the core of an approach that:
avoids loops
avoids VBA arrays
Say we have data in column A from A1 through A1000 with some empties in that range. Running:
Sub ChangeEmpty2Zero()
Dim rng As Range, rngE As Range
Set rng = Range("A1:A1000")
On Error Resume Next
Set rngE = rng.SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not rngE Is Nothing Then rngE = 0
End Sub
will fill those empties with zeros.
NOTE:
If the "bottom of the column is outside UsedRange, that "bottom" will not be changed.You could adapt this to run on whatever ranges in whatever worksheets you need.

Related

Is there a way to transfer all values from one array to another, then erase the original array?

I'm running into a problem with a block of code I'm trying to develop at my job. Essentially, I'm creating a userform in excel where folks will enter data for railcars as they get loaded at a certain location (we'll call these "spot 1, spot 2, spot 3, etc.").
Sometimes they'll have to move that car to a different spot, in which case I want them to be able to keep all the information on the railcar from the first/original entry, and then erase the data from the original spot once that's done.
To accomplish this in a more streamlined fashion, I've established arrays for each of the 5 spots that reference all the cells they're entering data into on the userform:
Dim spot1information(14)
spot1information(0) = UserForm.ProductType1.Value
spot1information(1) = UserForm.ProductID1.Value
spot1information(2) = UserForm.BatchID1.Value
etc....
Dim spot2information(14)
spot2information(0) = UserForm.ProductType2.Value
spot2information(1) = UserForm.ProductID2.Value
spot2information(2) = UserForm.BatchID2.Value
etc....
And so forth for all five spots. I don't know if this makes things more difficult, but note that these array values aren't all of the same type. For instance, index (0) will be a string, but index (10) is a DATETIME and index (12) is defined as Long.
So say that they are moving a car from spot 1 to spot 2. In short, I want the code to do the following:
Replace the values of indices 0 - 6 in spot2information (which is currently empty) with the values of indices 0 - 6 in spot1information (which the user has filled on the userform).
I'm only interested in carrying over indices 0-6 because they contain the pertinent railcar information
Empty every value of spot1information to ""
To accomplish this, I tried the following code and a few variations thereof:
If OriginalSpot.Value = 1 Then
If DestinationSpot.Value = 2 Then
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
For Each i in spot1information
spot1information(i) = ""
Next
End If
End If
However, this keeps coming up with a type mismatch. I figure because the data in the spot2information array is empty, and the data in the spot1information array is not, but I'm not entirely sure of a way around this.
Update: I did what was suggested below and replaced: spot1information(i) = "" with Erase spot1information
The code now essentially works! The values of array "spot2information" are now the former values of "spot1information", with "spot1information" now empty.
The 2D array suggested below also works like a charm. New problem I've been facing is that array values are updating, but the userform isn't. (note: in the future I'll be posting this type of thing as a separate question, my apologies!)
Easier to manage this as a 2D array:
Sub Tester()
Dim spots(1 To 5, 0 To 14), n As Long, i As Long
'fill your spot arrays from the form....
For n = 1 To 5
spots(n, 0) = UserForm.Controls("ProductType" & n).Value
spots(n, 1) = UserForm.Controls("ProductID" & n).Value
spots(n, 2) = UserForm.Controls("BatchID" & n).Value
'etc etc
Next n
'swap a spot with another
Debug.Print spots(2, 1), spots(3, 1)
SwapSpots spots:=spots, fromSpot:=2, toSpot:=3
Debug.Print spots(2, 1), spots(3, 1)
End Sub
Sub SwapSpots(spots, fromSpot As Long, toSpot As Long)
Dim n As Long
For n = 0 To 6
spots(toSpot, n) = spots(fromSpot, n)
spots(fromSpot, n) = Empty 'empty the source slot value
Next n
End Sub
Assuming the DataType of the arrays is the same by Index i.e. index(0) is string for all spots, Index(2) is long for all spots, and so on.
If that is the case then this part should not produce any error:
For i = 0 to 6
spot2information(i) = spot1information(i)
Next
The error should be happening in this part more precisely in the line marked with #
For Each i in spot1information
spot1information(i) = "" '#
Next
and the reason for the error it seems to be that trying to assign a string value "" to a numeric type, given the "mismatch" error.
Using For Each i in spot1information indicates that you want to "Initiate" or Erase the entire array, therefore I suggest to use this line instead of the For…Next method.
Erase spot1information
In regards this:
But I've now run into a new problem, where the values on the userform haven't updated to reflect the new values stored in the array. Do I need to somehow "refresh" the userform?
You just updated the arrays, then you need to run the procedures used to update the values of the objects affected by both arrays in the UserForm.

LotusScript ans Two dimensional Array & subscription out or range error

Hello I have two dimensional array as below in LotusScript.
Counter = 0
While Not (ProcessingViewDoc Is Nothing )
Redim Preserve AllRecrods(Counter,0)
AllRecrods(Counter,0) = ProcessingViewDoc.Test1(0)
Redim Preserve AllRecrods(Counter,1)
AllRecrods(Counter,1) = ProcessingViewDoc.Test2(0)
Redim Preserve AllRecrods(Counter,2)
Set ProcessingViewDoc = ProcessingView.GetNextDocument(ProcessingViewDoc)
Counter = Counter +1
Wend
When It processes next document it does and reaches to counter 1 and second document it gives me error subscription out of range.
Here is global declaration of array.
Dim AllRecrods() As Variant
Here is the line when it gives error when it goes to loop second time.
Redim Preserve AllRecrods(Counter,0)
In addition to Richard's excellent answer, I would suggest a couple of things.
1) Instead of While Not (ProcessingViewDoc Is Nothing) (which contains two negatives, making it harder to read), use Do Until doc Is Nothing. It is much clearer.
2) If you use a list, you don't have to worry about redim of the array. You could make it a list of a custom data type, and if you use the UNID of the document as the key, you can quickly connect the values back to the originating document.
My code would look something like this:
--- Declarations ---
Type recordData
value1 As String
value2 As String
End Type
--- Main Code ---
Dim allRecords List As recordData
Dim unid as String
Do Until ProcessingViewDoc Is Nothing
unid = ProcessingViewDoc.UniqueID
allRecords(unid).value1 = ProcessingViewDoc.Test1(0)
allRecords(unid).value2 = ProcessingViewDoc.Test2(0)
Set ProcessingViewDoc = ProcessingView.GetNextDocument(ProcessingViewDoc)
Loop
You are using ReDim with the Preserve option and changing both of the dimensions. You can't do that.
From the documentation for the ReDim statement:
If Preserve is specified, you can change only the upper bound of the
last array dimension. Attempting to change any other bound results in
an error.
Also, the logic there is screwed up. You're doing three redims on every iteration, with the first one shrinking the second dimension back to zero on every iteration. Even if you weren't changing the first dimension, that would lose the data that you stored in AllRecrods( n ,1) because the preserve option can't keep data in a dimension that you shrink below the size that you've already used!
You should probably consider swapping your two dimensions, reversing them in your assignments, keeping the first dimension constant at 2, and eliminating two of your ReDim Preserve statements. I.e., just do one ReDim Preserve AllRecrods(2,counter) on each iteration of the loop.

Retrieving values of Mode Array in Excel

We have some spreadsheets with thousands of numbers where we would like to return the mode arrays. MODE.MULT can only handle 254 numbers.
Trying to overcome this, we split the mode arrays to check 252 cells of a single column. We were then going to check the Mode from among these numbers (understanding that this adds a degree of error, but considering the set size, we believe it to be small).
This led us to assigning a jagged array to the mode function, but we're having problems retrieving the values therein:
Dim ModeArray() As Variant 'Also tried ModeArray as not an array of variants but that left the same errors
Dim RowSet As Integer
RowSet = 2
Dim rr As Range
For n = 2 To 252
Set rr = Sheet5.Range(Sheet5.Cells(7, n), Sheet5.Cells(260, n))
If Application.WorksheetFunction.CountBlank(rr) < 253 Then
'Needed because a 1004 error will pop if there are no numbers in a particular column
ModeArray = Application.WorksheetFunction.Mode_Mult(rr)
For i = 1 To UBound(ModeArray)
Sheet5.Cells(RowSet, n).Value = ModeArray(i)
'We get a few different errors. E.g. sub script out of range errors or if (1)(1) is tested "Object doesn't support this property or method (Error 438)" even though the TypeName(ModeArray(i)) is Double
RowSet = 1 + RowSet
Next
RowSet = 2
End If
Next
We are only expecting 2-3 modes per column, but gave space for 5. That's not our problem, however. Trying to retrieve the information out of ModeArray doesn't work, and it's type is Variant().
How can we get the actual values of the mode out, so we can report them in another table? I'm aware I could put in array functions directly in the worksheet, but I'd like to avoid dealing with "N/A" values down stream, and I don't have a way to determine the length of the mode array in a function.
Alternatively, is there a way to skip this all together and retrieve the modes of a very large data set?
Edit:
I should note that the above script will work if there is only one Mode, it's a problem if there are more than one modes.
MODE.MULT can handle more than 254 numbers in each parameter - its limitation is that it cannot handle more than 254 different parameters.
Sub modes()
Dim modearray As Variant
Dim j As Long
modearray = Application.WorksheetFunction.Mode_Mult(Range("Sheet1!A:B"))
If UBound(modearray) = 1 Then
Debug.Print modearray(1)
Else
For j = LBound(modearray) To UBound(modearray)
Debug.Print modearray(j, 1)
Next j
End If
End Sub
This works (a little slowly) for several thousand numbers in cols A & B

Need help, Excel Formula, Count number of cells in a range that match the content of a different range

I am pretty good on Excel but this has got me.
I have a range of entries - $D7:$AH7
I want to know how many times these entries appear in this range - $K$7:$V$10
I have tried countif array but I think it is trying to match row with row rather than each entry in my range against the entire lookup range. I also tried transposing the lookup range onto the same line but then I think it tries to match column by column in the same fashion.
If sumproduct works I can't use that is too calculation extensive with this document.
I am happy to use a VBA solution if you have that suggestion.
Non VBA solution:
=SUMPRODUCT(COUNTIF($K$7:$V$10,$D7:$AH7))
VBA solution:
Founds how many times values from rng1 appears in rng2:
Can it ignore blanks?
UPD:
Function countEntries(rng1 As Range, rng2 As Range) As Long
Dim arr1, arr2, a1, a2
arr1 = rng1.Value
arr2 = rng2.Value
For Each a1 In arr1
If Trim(a1) <> "" Then
For Each a2 In arr2
If a2 = a1 Then
countEntries = countEntries + 1
Exit For
End If
Next
End If
Next
End Function
call it like this: =countEntries($D7:$AH7,$K$7:$V$10).
P.S. I use loop for determining whether array contains value or not rather than Application.Match/Application.CountIf because it's up to 10 times faster. See this link for details: Matching values in string array

Assigning values to a dynamically resized array in Excel VBA

I am trying to populate a two dimensional array of ranges. I don't know how big the array will need to be, so I am using the ReDim and Preserve functions to dynamically re-size the array as required.
I am encountering runtime error 91: "Object variable or With block variable not set" when I run the code.
I am not an experienced coder, but I have managed to isolate the error, and am sure it is coming from the pseudo code below.
Can anyone see any mistakes I have made that would produce the runtime error?
Dim ArrayName() as Range
Dim counter as Integer
If condition = True Then
counter = counter + 1
ReDim Preserve ArrayName(0, counter - 1)
ArrayName(0, counter - 1) = Cells(counter, counter) 'I get a runtime error here
End If
Thank you.
If you want to store ranges within your array you need to add Set before problem line in this way:
Set ArrayName(0, counter - 1) = Cells(counter, counter)
But if you want to store values of the cells you need to change declaration line to this:
Dim ArrayName() as Double 'or String or Variant depending on value type you need to keep in array

Resources