I am trying to filter an array of 20000 rows and 70 columns by a particular column. I want to copy the whole row to another array if the value in column 14 is "Hard". I came up with a very simple implementation and tried using it but Excel went Not Responding everytime crashed eventually. Then I added DoEvents in the loop so that excel does not crash. My code looks as follows for now. It would help a lot if the community could help me optimize it. It is taking way too much time right now.
Is there some other function I can use to slice the array, instead of Index, that would be working faster?
Dim arr_all() As Variant
Dim arr_Hard(1 To 20000) As Variant
Dim arr_Soft(1 To 20000) As Variant
Dim arr_Travel() As Variant
arr_all = wsCopyTo.Range("A2:BR20000").Value
m = 1
n = 1
For i = LBound(arr_all) To UBound(arr_all)
DoEvents
Select Case arr_all(i, 14)
Case "Hard"
arr_Hard(m) = Application.Index(arr_all, i)
m = m + 1
Case "Soft"
arr_Soft(n) = Application.Index(arr_all, i)
n = n + 1
End Select
Next i
The Solution
Sort the items in the original Worksheet by the Hard/Soft column (desc or asc no matter). If you can't sort for some reason then copy the original worksheet to a new one.
Find the row where Hard/Soft items end - simply count the number of Hard or Soft with COUNTIF, whichever comes first, and get the required ranges of cells FROM and TO for both Hard and Soft items
Use the wsCopyTo.Range("A[FROM]:BR[TO]").Value to simply copy the part of Hard and Soft to array arr_Hard and array arr_Hard
Related
I have an array that is
myarray (1 to 37, 1 to 44) as variant
I'm trying to speed up my spreadsheet so rather than using big tables with lots of formulas in I'm turning to arrays, calculating them in memory and then writing the array to the sheet like such.
'Write array to worksheet.
Sheet1.Range("A1").Resize(UBound(myarray, 1), UBound(myarray, 2)).Value = myarray
This is working well except I'm not sure how to write code to perform certain calculations on specific rows or columns of the array. So if I wanted to know the average of all the elements in row 2 for example, how would that be written?
Many thanks in advance.
P.S. I should say I know I can cycle through and perform the average like so but I am more talking about using the various worksheet functions on specific rows or columns, this is what I don't understand how to write.
Thanks but I've sussed it now. I place all the elements of that particular row or column into a new array then simply use the worksheet function command. So in this example I am using functions on each column of the myarray. I'll post this in case it helps anyone else with worksheet functions in arrays.
For i = 1 To 44
Erase Calc1Array()
ReDim Calc1Array(1 To 37)
For j = 1 To 37
Calc1Array(j) = MyArray(j, i)
Next j
Answer1 = Application.WorksheetFunction.Max(Calc1Array)
Answer2 = Application.WorksheetFunction.Average(Calc1Array)
Answer3 = Application.WorksheetFunction.StDev(Calc1Array)
Next i
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.
I am curious if I can copy multiple columns to a new array from an existing array in one iteration of a loop. Suppose we have the following general example:
Array1 contains 10,000 elements in column1, 10,000 elements in column2, and 10,000 elements in column 3, etc.
Let's say that I want a new array generated off that information, only I want only columns 1 and 2 populated. Can I do this by looping only once with a correctly dimensioned target array? For instance:
'Assume TargetArray has already been ReDimmed to the size of Array1 in the code prior
For i=0 to UBound(Array1)
TargetArray(x,1)= Array1(x,1)
TargetArray(x,2)=Array1(x,2)
Next
So can this be done in one step, or do I have to make a loop for each dimension I want to add to the array. Is there any speed savings by doing two operations per loop as stated above (assuming it works).
Thanks for all of your help!
Have you tried just using Range objects? I just made 100 values in columns A and B, and copy them to F and G. Or are you trying to plug values from the first three columns into an equation to give you values for the new two columns?
Sub CopyRange()
Dim Array1 As Range
Dim Array2 As Range
Set Array1 = Range("A1:B100")
Set Array2 = Range("F1:G100")
Array2.Value = Array1.Value
End Sub
Your example should work as what RubberDuck commented.
It is similar in below example which works at my end.
I can't fit it to comments so I have no choice to post it as answer.
Dim TargetArray ' declared as Variant type, not array of variants
ReDim TargetArray(0 To Ubound(Array1, 0), 0 To 1) ' for 2 columns
For i = 0 To Ubound(Array1, 1)
TargetArray(i, 0) = Array1(i, 0)
TargetArray(i, 1) = Array1(i, 1)
Next
Is this close to what you have? If so, then that should work.
Here is some code that I've written to create a rolling percentile evaluated at each row in the array. This code works fine, but it is SLOW! It takes 10 seconds to run 8,000 rows, and my computer is not slow. Does anybody know how to make this run faster? Caveats (All of these calculations need to be in memory, so no range references are allowed). I ran the percentile formula as a check from a fixed point, dragged down, and it is faster than this over the same data. Any tips?
Thank you so much in advance! :)
Dim Current_MACD_Percentile() As Double
ReDim Preserve Current_MACD_Percentile(1 To UBound(MoveAvg5, 1), 1 To 1)
Dim Current_MACD_Percentile_Arr() As Double
Dim Current_MACD_Percentile_Elm_Arr As Variant
For x = 1 To UBound(MoveAvg5)
For w = 1 To x
ReDim Preserve Current_MACD_Percentile_Arr(1 To 1, 1 To x)
Current_MACD_Percentile_Arr(1, x) = MACD_SD_Pct(x, 1)
Next w
With Application.WorksheetFunction
Current_MACD_Percentile(x, 1) = Application.WorksheetFunction.Percentile(.Index(Current_MACD_Percentile_Arr, 1, 0), 0.985)
End With
Next x
With Worksheets("Program Requirements")
.Range(.Cells(5 + m - 1, 19), .Cells(Last_Row, 19)).Value = Current_MACD_Percentile
End With
Your problem is the loop-within-a-loop. You mention in the question that you have 8000 rows, so I assume that UBound(MoveArg5) is around 8000. If so, the inner loop is running 1 + 2 + 3 + 4 + .... + 8000 = (8000 * 8001) / 2 = 32 million times! And you're redim'ing your array with each loop.
Plus, the inner loop isn't doing anything. You use "For w = 1 to x", but then inside the loop you don't reference w, so the inner loop is just repeating things needlessly.
Also, you have created two dimensional arrays, but then only seem to be using a single dimension (the other dimension is always set to 1) -- you could simplify by using one dimensional arrays.
Honestly, I'm not quite sure what you're trying to do here. But if this code "works" but is just slow, then remove the "For w=..." loop completely, leaving the inner part alone in the "For x = " loop. That should speed things up tremendously.
Or, explain which array contains your "raw" data.
EDIT:
Sorry for the delay, busy at work.
So if your data is in MACD_SD_Pct(x,1) here's an option:
Dim TempArr() as Double, R as integer, AnswerArr() as Double
Redim AnswerArr(UBound(MACD_SD_PCT,1))
For R=1 to UBound(MACD_SD_Pct,1)
Redim Preserve TempArr(R)
TempArr(R)=MACD_SD_Pct(R,1)
AnswerArr(R)=Application.WorksheetFunction.Percentile(TempArr,0.985)
Next R
Since you haven't posted any data I'm not certain, but this worked on 400 made up data points on a worksheet.
I have 3 one dimensional arrays.
Each contains information that corresponds to the other 2 arrays.
e.g Array 1 contains a customer first name
Array 2 contains a customer last name
Array 3 contains the customer phone number.
This is not my actual example but is easiest to explain.
How do I sort all three arrays so that they are sorted in order by say customer last name.
If Mr Smith is sorted and has moved from position 10 to position 5 in the lastname array, I would expect his phone number and first name to also be in position 5 in the respective arrays.
I am dealing with arrays with 10,000's of items so I would like to avoid looping (my current method) as this is incredibly slow.
Hoping to use the array.sort methods.
Can someone help me?
Ok - So I have tried to use a new data Type but am still at a loss how I can instantly filter using this. Below is my sample code which has a couple of issues. If someone can resolve - it would love to learn how you did it.
The purpose of the code is to return an array containing grouped issues.
For simplicity I have assumed in the example that each constant found is an issue.
If an issue is found, combine it with other issues found on that same worksheet.
e.g The number 2 is found in both cells A1 and A2 on sheet 1. The array should return A1:A2.
If the issues are found in A1 on sheet 1 and A2 in sheet 2, two seperate array entries would be returned.
Test File and Code Here
Public Type Issues
ws_Sheet As Integer
rng_Range As String
s_Formula As String
s_Combined As String
d_ItemCount As Double
End Type
Sub IssuesFound()
Dim MyIssues() As Issues
Dim i_SheetCount As Integer
Dim s_Formula As String
Dim rng_Range As Range
Dim d_IssueCounter As Double
Dim s_SearchFor As String
Dim a_TempArray() As Issues
Dim d_InsertCounter As Double
d_IssueCounter = -1
' Loop All Sheets Using A Counter Rather Than For Each
For i_SheetCount = 1 To ActiveWorkbook.Sheets.Count
' Loop all Constants On Worksheet
For Each rng_Range In Sheets(i_SheetCount).Cells.SpecialCells(xlCellTypeConstants, 23)
If d_IssueCounter = -1 Then
' First Time and Issue Is Found, Start Recording In An Array
d_IssueCounter = d_IssueCounter + 1
ReDim MyIssues(0)
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Going To Look For Issues Found On The Same Sheet with The Same Constant Value
s_SearchFor = i_SheetCount & "#" & rng_Range.Value
' HELP HERE: Need To Ideally Return Whether The Above Search Term Exists In The Array
' Without looping, and I want to return the position in the array if the item is found
a_TempArray = MyIssues 'Filter(MyIssues.s_Combined, s_SearchFor, True, vbTextCompare)
If IsVarArrayEmpty(a_TempArray) = True Then
' New Issue Found - Increase Counter By + 1
d_IssueCounter = d_IssueCounter + 1
' Increase The Array By 1
ReDim Preserve MyIssues(d_IssueCounter)
' Record The Information About The Constant Found. Sheet Number, Constant, Range, and also a combined string for searching and the array position
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Get The Array Position Where Other Issues With The Same Worksheet and Constant are Stored
d_InsertCounter = a_TempArray.d_ItemCount
' Add The New Found Constant To The Range Already Containing The Same Constants on This Worksheet
MyIssues(d_InsertCounter).rng_Range = Union(rng_Range, Range(MyIssues(d_InsertCounter).rng_Range)).AddressLocal
End If
End If
Next
Next
End Sub
Function IsVarArrayEmpty(ByRef anArray As Issues)
Dim i As Integer
On Error Resume Next
i = UBound(anArray, 1)
If Err.Number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
Sample Test File and Code Here
As suggested, you should not be using concurrent arrays at all. You should be defining a type with three properties and then creating a single array or collection of that type.
To answer your question though, there is no way to sort three arrays in concert but there is a way to sort two. What that means is that you can create a copy of the array that you want to use as keys and then use the copy to sort one of the other arrays and the original to sort the other. Check out the documentation for the Array.Copy overload that takes two arrays as arguments.
That said, copying the array and then sorting twice is a big overhead so you may not gain much, if anything, from this method. Better to just do it the right way in the first place, i.e. use a single array of a complex type rather than concurrent arrays of simple types. It's not 1960 any more, after all.