I have the following code:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp(i, 1) = ArrTest(i, k)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
Range("A1:B10") is an arbitrary range of numbers which should be ordered from small to big according to the WorksheetFunction.Small. With a single column this works perfectly fine. However, when applied as such (with a loop) the function copies values from the original range and the output is wrong.
Please try for yourself in an Excel sheet and tell me what I'm doing wrong or the function is wrong?
Thanks.
EDIT: I have it working with Application.Index which works in this particular example. See:
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp = Application.Index(ArrTest, 0, k)
'ArrTemp(i, 1) = ArrTest(i, 1)
ArrSmall(i, 1) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, 1)
Next i
Next k
End Sub
Which works fine. But when I apply the exact same logic to my original code it gives me the 1004 error: unable to get Small property. I have no clue.
to leave your code as much as it was (first example) works perfect:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2) '*
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp(i, 1) = ArrTest(i, k)
Next i
For i = 1 To 10
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
No errors or whatever at all...
the '* just is not used at all ArrTest = Range("A1:B10") will set automatically all ranges... still the ranges would be to big for the other ranges...
I get exactly the same output like in your second example...
EDIT:
If you want to sort the full range (not every column itself) then you need something like:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To UBound(ArrTest, 2)
For i = 1 To UBound(ArrTest)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTest, i + ((k - 1) * UBound(ArrTest)))
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
Still I beleave the first code should do what you desire ;)
The way you are using the ReDim statement is creating too many elements in the arrays. Try using the LBound function and UBound function after setting the worksheet range values into the first variant array.
Option Explicit
Sub test()
Dim ArrTest() As Variant, ArrSmall() As Variant, ArrTemp() As Variant
Dim k As Long, i As Long
ReDim ArrTest(10, 2) '<~~ unnecessary if writing values from the worksheet
Debug.Print LBound(ArrTest, 1) & ":" & UBound(ArrTest, 1)
Debug.Print LBound(ArrTest, 2) & ":" & UBound(ArrTest, 2)
'^^ this results in 0:10, 0:2. Not 1:10, 1:2
ArrTest = Range("A1:B10").Value2 '<~~ make sure you are putting values in
Debug.Print LBound(ArrTest, 1) & ":" & UBound(ArrTest, 1)
Debug.Print LBound(ArrTest, 2) & ":" & UBound(ArrTest, 2)
'^^ this results in 1:10, 1:2.
ReDim ArrSmall(LBound(ArrTest, 1) To UBound(ArrTest, 1), _
LBound(ArrTest, 2) To UBound(ArrTest, 2))
ReDim ArrTemp(LBound(ArrTest, 1) To UBound(ArrTest, 1), 1 To 1)
For k = LBound(ArrTest, 2) To UBound(ArrTest, 2)
For i = LBound(ArrTest, 1) To UBound(ArrTest, 1)
ArrTemp(i, 1) = ArrTest(i, k)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
It depends on how you write the code.
Sub test()
With Range("A1:B10")
For Each Column In .Columns
x = x + 1
For i = 1 To Column.Cells.Count
Cells(i, 10 + x).Value = Application.Small(Column, i)
Next i
Next Column
End With
End Sub
Edit:
Although this would work with Range my goal is to extract the columns
from an array without having to return to a Sheet. I think this is
limited somehow
Adding Array approach too..
Sub test()
Dim vArr As Variant, varrTemp As Variant
vArr = Application.Transpose(Range("A1:B10").Value)
For i = LBound(vArr, 1) To UBound(vArr, 1)
varrTemp = Application.Index(Application.Transpose(vArr), , i)
For x = LBound(varrTemp) To UBound(varrTemp)
Cells(x, 10 + i).Value = Application.Small(varrTemp, x)
Next x
Next i
End Sub
Related
My goal is to select a column of about 300,000 cells and round each cell's value to two decimal places.
I found that looping an array is far faster than looping through cells.
It is much faster if I have the whole array post its data into the cells after the loop rather than during because again posting any data in a loop takes too much time.
Is there a way to write all the values from the new array ("varArray") after the loop is completed?
Sub RoundedTwoDecimalPlaces()
Dim i As Integer
Dim MyArray() As Variant ' Declare dynamic array.
Dim LastRow As Integer
Dim lStart As Double
Dim lEnd As Double
lStart = Timer
LastRow = Cells(1, Selection.Column).End(xlDown).Row
MyArray = Range("a1:a8").Value2
ReDim MyArray(LastRow) ' Resize to x amount of elements.
For i = 1 To LastRow
MyArray(i) = Round(Cells(i, Selection.Column), 2) ' Initialize array.
Next i
''this is where I can't get my array to post into the cells dynamically.
Selection.Value = MyArray()
''to see the amount of time it takes to finish.
'' My goal is to do 300,000 lines quickly
lEnd = Timer
Debug.Print "Duration = " & (lEnd - lStart) & " seconds"
End Sub
You can get the array directly from the range and then restore the altered values:
Sub RoundedTwoDecimalPlaces()
Dim i As Integer
Dim arr As Variant
Dim lStart As Double
Dim ws As Worksheet, col as Long
Set ws = ActiveSheet
col = Selection.Column
lStart = Timer
With ws.Range(ws.Cells(1, col), ws.Cells(1, col).End(xlDown))
arr = .Value
For i = 1 to Ubound(arr, 1)
arr(i, 1) = Round(arr(i, 1), 2)
Next i
.Value = arr
end with
Debug.Print "Duration = " & (Timer - lStart) & " seconds"
End Sub
Here is how I did it using #Tim Williams Code.
I had to loop it because the array has a max character limit.
Here is the finished code:
Sub loopthrough()
Dim i As Integer
Dim arr As Variant
Dim arr1 As Variant
Dim arr2 As Variant
Dim lStart As Double
Dim ws As Worksheet, col As Long
LastRow = Cells(1, Selection.Column).End(xlDown).Row
Set ws = ActiveSheet
col = Selection.Column
lStart = Timer
If LastRow < 30001 Then
With ws.Range(ws.Cells(1, col), ws.Cells(1, col).End(xlDown))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
Else ''if selection is more than 30,000 lines.
n = 1
Z = 30000
Do While Z < LastRow
With ws.Range(ws.Cells(n, col), ws.Cells(Z, col))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
n = n + 30000
Z = Z + 30000
Loop
With ws.Range(ws.Cells(n, col), ws.Cells(n, col).End(xlDown))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
End If
Debug.Print "Duration = " & (Timer - lStart) & " seconds"
End Sub
New to VBA. I'm trying to create an array of rows.
Basically, I have an entire sheet and want to take all the rows that start with a certain value ("MA") in column 8.
I eventually want to manipulate that array (as if it were a range), and paste it somewhere else in the sheet. Can anyone help? Here's my code so far:
Dim top0M As Variant
ReDim top0M(1 To 1) As Variant
For i = 4 To Rows.Count
If Cells(i, 8).Value Like "MA*" Then
top0M(UBound(top0M)) = Rows(i)
ReDim Preserve top0M(1 To UBound(top0M) + 1) As Variant
End If
Next i
This code runs but I'm not sure how to debug it to know if I even have the right rows inside. Can I paste these rows as if they were a range?
This sets the range and loads the whole into an array then it loads a different array with the lines that you want:
With ActiveSheet 'This should be changed to the name of the worksheet: Worksheets("MySheet")
Dim rng As Range
Set rng = .Range(.Cells(4, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, .Cells(4, .Columns.Count).End(xlToLeft).Column))
Dim tot As Variant
tot = rng.Value
Dim top0M As Variant
ReDim top0M(1 To Application.CountIf(.Range("H:H"), "MA*"), 1 To UBound(tot, 2)) As Variant
Dim k As Long
k = 1
Dim i As Long
For i = LBound(tot, 1) To UBound(tot, 1)
If tot(i, 8) Like "MA*" Then
Dim j As Long
For j = LBound(tot, 2) To UBound(tot, 2)
top0M(k, j) = tot(i, j)
Next j
k = k + 1
End If
Next i
End With
'to print to a sheet just assign the values:
Worksheets("sheet1").Range("A1").Resize(UBound(top0M, 1), UBound(top0M, 2)).Value = top0M
Try this code
Sub Test()
Dim x As Variant
x = ActiveSheet.Range("A4").CurrentRegion.Value
x = FilterArray(x, 8, "MA*", True)
ActiveSheet.Range("K14").Resize(UBound(x, 1), UBound(x, 2)).Value = x
End Sub
Function FilterArray(ByVal myRefArr As Variant, ByVal col As Integer, ByVal refValue As String, ByVal equal As Boolean) As Variant
Dim a As Variant
Dim i As Long
Dim j As Long
Dim n As Long
On Error Resume Next
n = 1
If refValue = "" Then
FilterArray = myRefArr
Else
ReDim a(1 To UBound(myRefArr, 1), 1 To UBound(myRefArr, 2))
For i = 1 To UBound(a, 1)
If IIf(equal, UCase(myRefArr(i, col)) Like UCase(refValue), Not (UCase(myRefArr(i, col)) Like UCase(refValue))) Then
For j = 1 To UBound(a, 2)
a(n, j) = myRefArr(i, j)
Next j
n = n + 1
End If
Next i
a = Application.Transpose(a)
ReDim Preserve a(1 To UBound(a, 1), 1 To n - 1)
a = Application.Transpose(a)
FilterArray = a
End If
On Error GoTo 0
End Function
The following line of code will allocate an array as one dimensional sortedArr = WorksheetFunction.Transpose(lstIssues1.List). This line of code allocates an array as two dimensional arrIssues = Table.ListColumns(Table.ListColumns(strNumber).Range.column).DataBodyRange
For each of the above lines I call a bubble sort function. But it errors on one or the other depending on whether I put
If Arr(i) > Arr(j) Then ...
Or
If Arr(i, 1) > Arr(j, 1) Then ...
I can loop to fill arrIssues. But I am wondering if it's possible to fill it as a one dimensional array without looping.
UPDATE
Here is the code I am having trouble with
Private Sub cmdRemove_Click()
Dim SortedArr() As Variant
With lstPrevious
If .ListIndex = -1 Then Exit Sub
For i = .ListCount - 1 To 0 Step -1
If .Selected(i) = True Then
lstAdditional.AddItem .List(i)
.RemoveItem (i)
End If
Next i
End With
ReDim SortedArr(lstAdditional.ListCount - 1)
SortedArr = Application.Transpose(lstAdditional.List) 'ERROR Type Mismatch
Call BubbleSort(SortedArr)
Me.lstAdditional.List = SortedArr
txtFocus.SetFocus
End Sub
Public Sub BubbleSort(Arr)
Dim strTemp As String
Dim lngMin As Long
Dim lngMax As Long
lngMin = LBound(Arr)
lngMax = UBound(Arr)
For i = lngMin To lngMax
For j = i + 1 To lngMax
If Arr(i) > Arr(j) Then
strTemp = Arr(i)
Arr(i) = Arr(j)
Arr(j) = strTemp
End If
Next j
Next i
End Sub
Strangely, I use the same logic in another userform and it works. Sorry for the lack of clarity.
For each of the above lines I call a bubble sort function. But it errors on one or the other depending on whether I put If Arr(i) > Arr(j) Then ... Or If Arr(i, 1) > Arr(j, 1) Then ...
Your values of i, j are incorrect and hence it errors out. I am guessing that you are getting Subscript out of range error.
Here is a simple way to reproduce the error that you are getting.
Sub Sample()
Dim i As Long, j As Long, k As Integer
Dim MyAr As Variant
MyAr = Range("A1:A5").Value
For i = LBound(MyAr) To UBound(MyAr)
For j = LBound(MyAr) To UBound(MyAr)
If MyAr(j, 1) > MyAr(j + 1, 1) Then
k = MyAr(j, 1)
MyAr(j, 1) = MyAr(j + 1, 1)
MyAr(j + 1, 1) = k
End If
Next
Next
End Sub
The correct way is to loop till UBound(MyAr) - 1
Sub Sample()
Dim i As Long, j As Long, k As Integer
Dim MyAr As Variant
MyAr = Range("A1:A5").Value
For i = LBound(MyAr) To (UBound(MyAr) - 1)
For j = LBound(MyAr) To (UBound(MyAr) - 1)
If MyAr(j, 1) > MyAr(j + 1, 1) Then
k = MyAr(j, 1)
MyAr(j, 1) = MyAr(j + 1, 1)
MyAr(j + 1, 1) = k
End If
Next
Next
End Sub
I have the following code below,
I want to get the entire row not just column 1 of the original array, how would i do this?
Sub Example1()
Dim arrValues() As Variant
Dim lastRow As Long
Dim filteredArray()
Dim lRow As Long
Dim lCount As Long
Dim tempArray()
lastRow = Sheets("Raw Data").UsedRange.Rows(Sheets("Raw Data").UsedRange.Rows.Count).Row
arrValues = Sheets("Raw Data").Range(Cells(2, 1), Cells(lastRow, 21)).Value
' First use a temporary array with just one dimension
ReDim tempArray(1 To UBound(arrValues))
For lCount = 1 To UBound(arrValues)
If arrValues(lCount, 3) = "phone" Then
lRow = lRow + 1
tempArray(lRow) = arrValues(lCount, 1)
End If
Next
' Now we know how large the filteredArray needs to be: copy the found values into it
ReDim filteredArray(1 To lRow, 1 To 1)
For lCount = 1 To lRow
filteredArray(lCount, 1) = tempArray(lCount)
Next
Sheets("L").Range("A2:U" & 1 + lRow) = filteredArray
End Sub
The ReDim statement can add records on-the-fly with the PRESERVE parameter but only into the last rank. This is a problem as the second rank of a two dimensioned array is typically considered the 'columns' while the first are the 'rows'.
The Application.Transpose can flip rows into columns and vise-versa but it has limitations. (see here and here)
A simple function to transpose without these limitations is actually very easy to build. All you really need are two arrays and two nested loops to flip them.
Sub Example1()
Dim arrVALs() As Variant, arrPHONs() As Variant
Dim v As Long, w As Long
With Sheets("Raw Data").Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, 21).Offset(1, 0)
arrVALs = .Cells.Value
'array dimension check
'Debug.Print LBound(arrVALs, 1) & ":" & UBound(arrVALs, 1)
'Debug.Print LBound(arrVALs, 2) & ":" & UBound(arrVALs, 2)
'Debug.Print Application.CountIf(.Columns(3), "phone") & " phones"
End With
End With
ReDim arrPHONs(1 To UBound(arrVALs, 2), 1 To 1)
For v = LBound(arrVALs, 1) To UBound(arrVALs, 1)
If LCase(arrVALs(v, 3)) = "phone" Then
For w = LBound(arrVALs, 2) To UBound(arrVALs, 2)
arrPHONs(w, UBound(arrPHONs, 2)) = arrVALs(v, w)
Next w
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) + 1)
End If
Next v
'there is 1 too many in the filtered array
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) - 1)
'array dimension check
'Debug.Print LBound(arrPHONs, 1) & ":" & UBound(arrPHONs, 1)
'Debug.Print LBound(arrPHONs, 2) & ":" & UBound(arrPHONs, 2)
'Option 1: use built-in Transpose
'Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = Application.Transpose(arrPHONs)
'Option 2: use custom my_2D_Transpose
Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = my_2D_Transpose(arrPHONs)
End Sub
Function my_2D_Transpose(arr As Variant)
Dim a As Long, b As Long, tmp() As Variant
ReDim tmp(1 To UBound(arr, 2), 1 To UBound(arr, 1))
For a = LBound(arr, 1) To UBound(arr, 1)
For b = LBound(arr, 2) To UBound(arr, 2)
tmp(b, a) = Trim(arr(a, b))
Next b
Next a
my_2D_Transpose = tmp
End Function
So if you are in a hurry and the scope of your arrays is such that you will never reach the limits of Application.Transpose then by all means use it. If you cannot safely use transpose then use a custom function.
I want to assign values to arrays from a sheet using loop
I tried using this but gives error "Subscript out of Range"
i=1
With ws
Do While i <= 40
ReDim Preserve WorkID(1 To i)
ReDim Preserve Work(1 To i)
ReDim Preserve ComposerName(1 To i)
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
End With
I tried both ways to initialize but none of them worked
Initialize Type 1
Dim WorkID() As Variant
Dim Work() As Variant
Dim ComposerName() As Variant
Initialize Type 2
Dim WorkID(1 to 40) As Variant
Dim Work(1 to 40) As Variant
Dim ComposerName(1 to 40) As Variant
Also I tried without Redim as well like this but nothing worked:
i=1
With ws
Do While i <= 40
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
End With
Full Sub here :
Option Explicit
Sub Join()
Dim WorkID() 'Stores the workID from Works Sheet
Dim Work() 'Stores the work from Works Sheet
Dim ComposerName() 'Stores the composer from Works Sheet
Dim ConductorID() 'Stores the ConductorID from Conductors Sheet
Dim ConductorNames() 'Stores Conductor Names from Conductors Sheet
Dim CDWorkID() 'Stores CDWorkID from CD Sheet
Dim CDCondID() 'Stores CDConductor ID from CD Sheet
Dim i, j, k, m As Long
Dim ws, wcon, wcd, wj As Worksheet
Set ws = Sheets("Works")
Set wcon = Sheets("Conductors")
Set wcd = Sheets("CDs")
Set wj = Sheets("Join")
i = j = k = 1 'Initalize
ws.Activate
Do While i <= 40
ReDim Preserve WorkID(1 To i)
ReDim Preserve Work(1 To i)
ReDim Preserve ComposerName(1 To i)
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
wcon.Activate
Do While j <= 10
ReDim Preserve ConductorID(1 To j)
ReDim Preserve ConductorNames(1 To j)
ConductorID(j) = Range("A" & j + 1).Value
ConductorNames(j) = Range("B" & j + 1).Value
j = j + 1
Loop
wcd.Activate
Do While k <= 132
ReDim Preserve CDWorkID(1 To k)
ReDim Preserve CDCondID(1 To k)
CDWorkID(k) = Range("A" & k + 1).Value
CDCondID(k) = Range("B" * k + 1).Value
k = k + 1
Loop
wj.Activate
For i = LBound(CDWorkID) To UBound(CDWorkID)
Range("F" & i) = CDWorkID(i)
Next i
End Sub
RedDim Preserve is generally an expensive operation since it involves allocating space for a larger array and moving contents from the old array. It is almost always a bad idea to use it inside of a loop. Instead -- determine ahead of time how big the arrays need to be and ReDim just once. If you don't know ahead of time, make them larger than needed and then use a ReDim Preserve after the loop to trim them down to size. In your case, I would Redim the arrays before entering for loops (or even -- why not Dim them the right size to begin with?). Also -- prefix each range with the appropriate worksheet variable rather than activating each in turn. Something like:
Sub Join()
Dim WorkID() 'Stores the workID from Works Sheet
Dim Work() 'Stores the work from Works Sheet
Dim ComposerName() 'Stores the composer from Works Sheet
Dim ConductorID() 'Stores the ConductorID from Conductors Sheet
Dim ConductorNames() 'Stores Conductor Names from Conductors Sheet
Dim CDWorkID() 'Stores CDWorkID from CD Sheet
Dim CDCondID() 'Stores CDConductor ID from CD Sheet
Dim i As Long
Dim ws, wcon, wcd, wj As Worksheet
Set ws = Sheets("Works")
Set wcon = Sheets("Conductors")
Set wcd = Sheets("CDs")
Set wj = Sheets("Join")
ReDim WorkID(1 To 40)
ReDim Work(1 To 40)
ReDim ComposerName(1 To 40)
For i = 1 To 40
WorkID(i) = ws.Range("A" & i + 1).Value
Work(i) = ws.Range("B" & i + 1).Value
ComposerName(i) = ws.Range("C" & i + 1).Value
Next i
ReDim ConductorID(1 To 10)
ReDim ConductorNames(1 To 10)
For i = 1 To 10
ConductorID(i) = wcon.Range("A" & i + 1).Value
ConductorNames(i) = wcon.Range("B" & i + 1).Value
Next i
ReDim CDWorkID(1 To 132)
ReDim CDCondID(1 To 132)
For i = 1 To 132
CDWorkID(k) = wcd.Range("A" & i + 1).Value
CDCondID(k) = wcd.Range("B" & i + 1).Value
Next i
For i = LBound(CDWorkID) To UBound(CDWorkID)
wj.Range("F" & i) = CDWorkID(i)
Next i
End Sub
Range("B" * k + 1).Value has a typo - you meant Range("B" & k + 1).Value. This makes the range raise an "type" error.
Eliminating this makes the code run without error - I suspect the error message is incorrect.
BTW, there is another pitfall (which does not lead to a runtime error, at least not for the code shown):
Dim i, j, k, m As Long
Dim ws, wcon, wcd, wj As Worksheet
will NOT declare i, j, kas Integer but as Variants. Same for ws, wcon, wcd which are variants and NOT worksheet objects.