VBA: ReDimming multiple keyed collections = Error 9 'Subscript Out of Range'? - vba
I am attempting to modify some keyed collection code (thanks #Mat'sMug!) to make it loop through 3 distinct ranges and then put the values into respective variables. The first keyed collection works fine, but the second one (and I'm guessing the third once it gets past the second) spit an error out at the line ReDim ccAddresses(0 To ccRecipients.Count - 1)
Private Sub AddUniqueItemToCollectionzz(ByVal value As String, ByVal items As Collection)
On Error Resume Next
items.Add value, Key:=value
On Error GoTo 0
End Sub
Sub Sampletest()
Dim toRecipients As Collection
Set toRecipients = New Collection
Dim ccRecipients As Collection
Set ccRecipients = New Collection
Dim cc2Recipients As Collection
Set cc2Recipients = New Collection
'===============Copy primary email addresses=============
With toRecipients
For Each cell In Range("H1:H350")
If cell.value Like "*#*.*" Then
AddUniqueItemToCollectionzz cell, toRecipients
End If
Next
End With
ReDim toAddresses(0 To toRecipients.Count - 1)
Dim toAddress As Variant, toItem As Long
For Each toAddress In toRecipients
toAddresses(toItem) = CStr(toAddress)
toItem = toItem + 1
Next
Dim sendToPrim As String
sendToPrim = Join(toAddresses, ";")
'=====================Copy cc email addresses======================
With ccRecipients
For Each cell In Range("J1:J350")
If cell.value Like "*#*.**" Then
AddUniqueItemToCollectionzz cell, ccRecipients
End If
Next
End With
ReDim ccAddresses(0 To ccRecipients.Count - 1)
Dim ccAddress As Variant, ccItem As Long
For Each ccAddress In ccRecipients
ccAddresses(ccItem) = CStr(ccAddress)
ccItem = ccItem + 1
Next
Dim sendToCC As String
sendToCC = Join(ccAddresses, ";")
'====================Copy cc2 email addresses================
With cc2Recipients
For Each cell In Range("A1:a350")
If cell.value Like "*.uSA.TACO*" Then
AddUniqueItemToCollectionzz cell, cc2Recipients
End If
Next
End With
ReDim cc2Addresses(0 To cc2Recipients.Count - 1)
Dim cc2Address As Variant, cc2Item As Long
For Each ccAddress In cc2Recipients
cc2Addresses(cc2Item) = CStr(cc2Address)
cc2Item = cc2Item + 1
Next
Dim sendToCC2 As String
sendToCC2 = Join(cc2Addresses, ";")
When dimensioning or redimensioning with Dim(x to y) or ReDim(x to y) y must be greater than or equal to x. So check your code by adding following line before the ReDim ccAddresses(0 To ccRecipients.Count - 1) statement.
Debug.Assert ccRecipients.Count >0
Related
excel , extract the time Break from one cell in excel sheet?
I have an Excel sheet like below and I need only the three "Break" times even if it meant to delete every thing except those three Breaks in every cell. Function GetBreaksTime(txt As String) Dim i As Long Dim arr As Variant arr = Split(txt, "Break") If UBound(arr) > 0 Then ReDim startTimes(1 To UBound(arr)) As String For i = 1 To UBound(arr) startTimes(i) = WorksheetFunction.Trim(Replace(Split(arr(i), "-")(0), vbLf, "")) Next GetBreaksTime = startTimes End If End Function This what I got until now but it wont work on every cell and it takes wrong values. So any idea how to do this?
If you split the cell value by vbLf the break time will always follow a line containing "Break". The following should work: Sub TestGetBreakTimes() Dim CellValue As String CellValue = Worksheets("Sheet1").Range("A1").Value Dim BreakTimes As Variant BreakTimes = GetBreakTimes(CellValue) Debug.Print Join(BreakTimes, vbLf) 'the join is just to output the array at once. 'to output in different cells loop through the array Dim i As Long For i = 0 To UBound(BreakTimes) Cells(3 + i, "A") = BreakTimes(i) Next i 'or for a even faster output use Range("A3").Resize(UBound(BreakTimes) + 1).Value = WorksheetFunction.Transpose(BreakTimes) End Sub Function GetBreakTimes(InputData As String) As Variant Dim BreakTimes() As Variant ReDim BreakTimes(0) Dim SplitArr As Variant SplitArr = Split(InputData, vbLf) 'split by line break If UBound(SplitArr) > 0 Then Dim i As Long For i = 0 To UBound(SplitArr) If SplitArr(i) = "Break" Then 'if line contains break then next line is the time of the break If BreakTimes(0) <> vbNullString Then ReDim Preserve BreakTimes(UBound(BreakTimes) + 1) BreakTimes(UBound(BreakTimes)) = SplitArr(i - 1) 'collect break time End If Next i GetBreakTimes = BreakTimes End If End Function To analyze a complete range you must loop through your row 2 Sub GetAllBreakTimes() Dim ws As Worksheet Set ws = ThisWorkbook.Worksheets("Sheet1") Dim LastCol As Long LastCol = ws.Cells(2, ws.Columns.Count).End(xlToLeft).Column Dim BreakTimes As Variant Dim iCol As Long For iCol = 1 To LastCol BreakTimes = GetBreakTimes(ws.Cells(2, iCol).Value) ws.Cells(3, iCol).Resize(UBound(BreakTimes) + 1).Value = WorksheetFunction.Transpose(BreakTimes) Next iCol End Sub
VBA to extract names from a list at random isn't working
I want to randomly extract 280 names from a list (dynamic range) of thousands of authors, then concatenate them into 1 string with each name separated by OR. So far I'm stuck at the first part. When I run the following code, nothing happens. Can anyone explain why? Thanks. Sub Authors() Dim InputRange As Range Dim AuthorCount As Long Dim AuthorRange As Excel.Range Set InputRange = Range("A:A") AuthorCount = Application.WorksheetFunction.CountA(InputRange) - 1 Set AuthorRange = ThisWorkbook.Worksheets("Sheet").Range("A2:A" & AuthorCount) Const nItemsToPick As Long = 280 Dim Authorlist As Variant Dim i As Long ReDim Authorlist(1 To nItemsToPick) For i = 1 To nItemsToPick Authorlist(i) = AuthorRange.Cells(Int(AuthorCount * Rnd + 1), 1) Next i End Sub
Using the below code Sub PickSample(intSampleSize As Integer, intPicks As Integer) Dim arr() As String Dim intCounter As Integer ReDim arr(intPicks) For intCounter = 0 To UBound(arr) arr(intCounter) = CStr(Int((intSampleSize - (i - 1)) * Rnd + 1) + (i - 1)) Next intCounter Debug.Print Join(arr(), ",") End Sub I ran this on the following, PickSample(10,5) and got the following, showing the duplicate possibilities, this will become less as the difference between picks and samples increases. 9,9,6,10,10,2 5,3,6,7,2,3 10,4,5,8,0,6 9,8,4,10,9,0 0,8,8,7,0,4 7,5,6,3,3,8 If your selection is 280, but the data set is only 300, dupes still arise PickSample 300,280 228,**92**,248,216,269,66,**107**,166,**107**,61,174,189,41,18,190,252,192,127,56,149,292,231,114,145,164,202,11,194,270,102,35,128,232,**107**,124,225,131,216,152,52,83,26,294,85,186,**92**,256,96,239,52,90,21,148,136,179,9,95,40,98,228,188,290,249,166,182,57,271,95,180,179,230,215,206,228,77,165,153,170,84,125,105,292,156,175,139,9,113,41,196,46,59,112,28,185,211,132,126,101,210,64,13,266,13,138,222,227,247,63,141,261,249,123,139,105,75,242,163,162,188,26,214,77,17,82,289,98,194,109,111,98,64,63,127,185,72,206,177,181,23,13,46,74,120,175,86,251,270,158,116,0,75,49,194,295,93,72,264,39,171,182,183,208,255,29,118,247,80,204,119,251,130,251,65,220,270,65,295,290,262,157,195,137,47,193,184,257,110,15,152,16,112,135,89,291,3,195,184,160,8,215,94,295,87,109,96,106,70,178,211,80,173,173,298,280,75,243,231,122,189,148,150,40,291,53,177,205,32,195,222,234,129,24,150,172,17,124,35,43,94,298,181,82,125,141,19,137,131,284,82,52,152,103,154,119,78,20,192,109,164,265,127,178,114,17,32,43,43,228,79,41,12,208,254,155,240,296,157,20,188,99,83 4,50,49,153,122,31,83,193,255,149,56,269,112,97,232,65,134,71,264,183,112,117,259,176,280,155,99,261,77,78,53,104,0,223,253,83,211,121,244,223,131,23,123,102,213,93,240,45,178,287,73,282,34,296,190,180,271,173,73,258,22,132,228,73,113,119,158,81,174,63,23,269,33,196,271,69,285,254,132,148,231,251,115,58,98,124,45,186,29,61,208,151,55,298,141,1,128,86,226,268,247,53,32,3,45,113,56,294,262,175,219,43,77,8,249,235,238,100,135,167,241,169,61,62,109,172,103,158,128,172,15,164,62,289,280,298,252,123,242,297,77,52,209,5,102,208,33,33,87,120,168,93,88,243,93,113,120,253,123,218,198,122,286,194,155,67,175,225,137,272,85,200,267,84,110,4,88,296,229,174,182,80,152,238,258,28,163,125,22,135,210,150,122,284,296,178,160,185,26,55,85,5,45,126,165,168,235,12,122,17,93,181,155,179,99,273,231,173,129,220,49,17,73,228,286,103,205,238,10,239,145,62,181,273,284,196,4,199,290,2,287,22,88,175,243,12,16,169,94,124,153,220,135,97,22,123,172,229,174,196,243,125,239,217,208,219,57,232,21,74,286,246,66,55,71,278,77,77,215,200,232 209,294,73,160,32,300,203,4,173,30,31,240,85,13,89,114,90,285,294,120,83,48,49,194,123,124,214,98,190,62,55,175,24,137,272,78,236,114,87,276,190,188,128,29,168,209,275,251,6,163,275,129,204,151,154,139,106,121,81,16,73,294,18,117,109,147,46,142,77,189,163,47,282,197,152,117,32,235,138,226,179,250,5,63,22,31,99,38,0,161,197,163,249,24,57,204,136,107,45,212,279,159,26,228,120,139,148,62,99,28,177,51,279,29,133,82,262,225,82,202,77,27,9,97,237,89,70,144,76,102,13,145,62,260,177,227,279,99,163,24,190,123,289,34,277,186,104,44,144,66,299,39,8,103,164,277,162,122,255,248,202,217,300,102,149,124,209,53,127,163,245,162,128,153,68,186,147,204,266,111,91,88,45,159,67,175,109,263,143,57,205,224,184,235,48,243,60,287,19,18,238,114,139,35,34,52,14,215,160,168,65,140,224,226,120,271,224,26,191,214,4,129,120,82,296,241,209,125,221,83,107,130,284,36,194,104,31,55,23,130,288,163,148,292,65,114,119,84,151,41,155,290,167,273,197,132,208,19,227,210,149,46,67,98,236,15,155,227,241,97,292,242,203,272,263,125,37,287,239,209,120 Using a dictionary to handle the dupes, using this code Sub PickSample(intSampleSize As Integer, intPicks As Integer) Dim dicPicked As New Scripting.Dictionary Dim arr() As String Dim intCounter As Integer Dim intPicked As Integer ReDim arr(intPicks) For intCounter = 0 To UBound(arr) RetryPick: intPicked = CStr(Int((intSampleSize - (i - 1)) * Rnd + 1) + (i - 1)) If dicPicked.Exists(CStr(intPicked)) Then GoTo RetryPick Else dicPicked.Add CStr(intPicked), 1 arr(intCounter) = intPicked End If Next intCounter Debug.Print Join(arr(), ",") dicPicked.RemoveAll Set dicPicked = Nothing End Sub Gives results, hopefully dupe free PickSample 300,280 203,125,69,114,26,208,39,219,36,174,220,113,24,74,104,282,128,112,223,205,200,147,44,143,152,162,157,300,70,54,108,177,13,276,153,91,7,168,89,145,127,12,16,257,187,229,61,213,117,214,254,171,59,242,23,51,224,52,185,165,193,189,21,296,63,173,160,280,190,232,235,141,256,56,87,98,32,5,267,195,77,120,197,82,288,68,57,136,132,182,122,15,47,48,261,96,110,258,49,105,155,86,186,97,225,80,264,140,11,46,199,230,275,19,34,83,222,66,116,294,298,259,292,271,272,84,115,101,124,43,183,71,289,291,25,188,55,158,150,216,243,92,58,0,290,148,255,149,250,167,27,233,228,265,9,299,65,283,62,88,207,240,109,179,161,178,268,278,175,139,237,234,169,297,269,281,184,262,270,164,202,279,253,295,196,212,8,274,159,75,172,163,130,38,154,73,99,247,249,263,273,67,40,20,221,138,14,33,218,286,227,251,94,166,209,156,211,37,137,90,131,111,107,2,215,85,146,100,293,204,231,285,79,53,126,60,239,260,248,78,4,217,29,64,121,226,201,210,45,206,134,17,1,192,246,3,35,191,236,93,28,41,244,287,129,277,142,118,6,81,18,135,181,241,180,103,50,252,31,95,30 44,278,132,10,232,56,146,193,284,276,236,155,79,117,102,61,119,200,229,131,138,133,235,173,204,34,7,98,3,202,167,143,130,30,126,206,13,262,221,166,174,298,111,116,39,288,263,76,47,170,89,268,154,253,52,91,217,148,12,22,83,33,77,264,85,214,55,127,279,251,101,86,230,35,172,59,198,62,286,296,220,29,191,242,271,5,54,84,297,158,38,270,231,107,95,110,57,129,9,273,53,269,68,4,234,228,211,207,70,153,151,194,179,128,169,63,142,109,145,58,186,24,245,60,87,0,17,246,225,222,218,184,258,26,161,226,247,31,144,178,223,122,88,124,137,210,293,94,99,213,190,281,80,72,104,40,6,123,290,259,254,45,78,66,227,289,261,141,65,135,8,274,69,257,203,168,196,42,248,67,73,125,37,11,287,181,92,291,238,108,212,1,118,28,216,244,164,249,240,150,46,74,277,36,189,188,255,224,195,260,15,175,267,280,49,180,27,165,50,113,243,201,237,149,205,156,199,292,136,48,71,75,285,41,81,239,209,185,266,160,176,152,171,163,100,2,32,183,16,97,19,294,187,20,282,272,157,182,121,140,106,112,265,295,51,21,256,64,241,114,162,90,252,115,25,82,103,23,299,120,197 57,241,105,1,247,289,284,72,89,68,101,225,295,242,290,5,291,217,267,87,62,80,24,106,103,38,285,197,286,300,151,222,219,254,201,113,195,245,243,15,179,98,145,192,74,118,142,109,70,58,11,4,154,277,129,115,250,202,293,163,181,168,288,268,281,112,79,49,60,175,236,23,266,186,59,167,190,187,41,228,174,157,48,231,165,253,227,171,66,176,135,238,120,258,19,110,194,164,131,296,91,206,159,255,8,189,124,148,114,13,75,121,95,272,119,214,50,117,279,213,205,133,96,196,137,173,246,218,233,77,27,264,141,184,193,263,102,83,244,210,78,127,36,63,188,42,0,152,198,271,169,298,207,111,3,158,182,282,30,226,199,17,273,297,191,166,46,144,252,55,25,26,200,86,162,237,211,299,212,287,161,12,40,45,69,216,54,125,71,47,132,93,22,20,76,126,51,262,107,229,234,257,2,39,278,235,84,37,280,208,153,251,67,136,104,28,221,149,248,276,170,140,14,269,180,16,108,34,94,43,92,52,204,65,134,85,183,7,146,143,97,270,64,259,139,160,260,223,32,81,177,33,178,185,90,292,9,232,209,265,10,88,283,147,123,99,261,240,6,138,274,122,61,203,249,155,256,44,294,116,35
this function picks nItemsToPick random elements up from arr array and returns them into an array: Function PickNRandomElementsFromArray(arr As Variant, nItemsToPick As Long) As Variant Dim arrSize As Long Dim i As Long, iPick As Long Dim temp As String arrSize = UBound(arr) '<--| count the values as the array size If nItemsToPick > arrSize Then nItemsToPick = arrSize '<--| the items to be picked cannot exceed array size For i = 1 To nItemsToPick iPick = Int((arrSize - (i - 1)) * Rnd + 1) + (i - 1) '<--| pick a random number between 'i' and 'arrSize' ' swap array elements in slots 'i' and 'iPick' temp = arr(iPick) arr(iPick) = arr(i) arr(i) = temp Next i ReDim Preserve arr(1 To nItemsToPick) '<--| resize the array to first picked items PickNRandomElementsFromArray = arr '<--| return the array End Function which you can exploit as follows: Option Explicit Sub Authors() Dim Authors As Variant, AuthorsList As Variant With ThisWorkbook.Worksheets("Sheet") '<--| reference your relevant worksheet Authors = Application.Transpose(.Range("A2", .Cells(.rows.count, 1).End(xlUp)).Value) '<--| fill 'Authors' array with its column "A" values from row 2 down to its last not empty one AuthorsList = PickNRandomElementsFromArray(Authors, 280) '<--| fill 'AuthorsList' array with a selection of 280 random elements of 'Authors' .Range("B1").Value = Join(AuthorsList, " OR ") '<--| fill cell "B1" with a string build by concatenating 'AuthorsList' values delimited with " OR " End With End Sub that can be quite contracted (and made as much less readable) to: Sub Authors() With ThisWorkbook.Worksheets("Sheet") '<--| reference your relevant worksheet .Range("B1").Value = Join(PickNRandomElementsFromArray(Application.Transpose(.Range("A2", .Cells(.rows.count, 1).End(xlUp)).Value), 280), " OR ") End With End Sub
From your comments, you seem to want to concatenate the array of strings, then put it back into excel. This, put just before the End Sub, will put it into cell B1 for example: ThisWorkbook.Worksheets("Sheet1").Range("B1").Value = Join(Authorlist, "OR")
VBA - How to make a queue in an array? (FIFO) first in first out
I am trying to make a queue which is able to show the first in first out concept. I want to have an array which works as a waiting list. The patients who come later will be discharged later. There is a limitation of 24 patients in the room the rest will go to a waiting list. whenever the room is empty the first patients from the waiting room (the earliest) goes to the room. Here is the code that I have come up with so far. Any help is greatly appreciated. Dim arrayU() As Variant Dim arrayX() As Variant Dim arrayW() As Variant Dim LrowU As Integer Dim LrowX As Integer Dim LrowW As Integer 'Dim i As Integer Dim j As Integer Dim bed_in_use As Integer LrowU = Columns(21).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row LrowX = Columns(24).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row LrowW = Columns(23).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row ReDim arrayU(1 To LrowU) ReDim arrayX(1 To LrowX) ReDim arrayW(1 To LrowW) For i = 3 To LrowU arrayU(i) = Cells(i, 21) Next i i = 3 For i = 3 To LrowX arrayX(i) = Cells(i, 24) Next i i = 3 j = 3 For r = 3 To LrowW arrayW(r) = Cells(r, 23) Next r r = 3 i = 3 j = 3 For i = 3 To LrowX ' the number of bed in use is less than 24 (HH) If bed_in_use >= 24 Then GoTo Line1 For j = 3 To LrowU If bed_in_use >= 24 Then GoTo Line1 If arrayX(i) = arrayU(j) Then If Wait_L > 0 Then Wait_L = Wait_L - (24 - bed_in_use) Else bed_in_use = bed_in_use + 1 End If End If Next j Line1: For r = 3 To LrowW If bed_in_use < 24 Then Exit For If arrayX(i) = arrayW(r) Then bed_in_use = bed_in_use - 1 Wait_L = Wait_L + 1 End If Next r Cells(i, "Y").Value = bed_in_use Cells(i, "Z").Value = Wait_L Next i
Easiest way to do this would be to implement a simple class that wraps a Collection. You could wrap an array, but you'd end up either having to copy it every time you dequeued an item or letting dequeued items sit in memory. In a Class module (I named mine "Queue"): Option Explicit Private items As New Collection Public Property Get Count() Count = items.Count End Property Public Function Enqueue(Item As Variant) items.Add Item End Function Public Function Dequeue() As Variant If Count > 0 Then Dequeue = items(1) items.Remove 1 End If End Function Public Function Peek() As Variant If Count > 0 Then Peek = items(1) End If End Function Public Sub Clear() Set items = New Collection End Sub Sample usage: Private Sub Example() Dim q As New Queue q.Enqueue "foo" q.Enqueue "bar" q.Enqueue "baz" Debug.Print q.Peek '"foo" should be first in queue Debug.Print q.Dequeue 'returns "foo". Debug.Print q.Peek 'now "bar" is first in queue. Debug.Print q.Count '"foo" was removed, only 2 items left. End Sub
Would you not follow Comintern's "Class" approach (but I'd go with it!) you can stick to an "array" approach like follows place the following code in any module (you could place it at the bottom of you code module, but you'd be better placing it in a new module to call, maybe, "QueueArray"...) Sub Clear(myArray As Variant) Erase myArray End Sub Function Count(myArray As Variant) As Long If isArrayEmpty(myArray) Then Count = 0 Else Count = UBound(myArray) - LBound(myArray) + 1 End If End Function Function Peek(myArray As Variant) As Variant If isArrayEmpty(myArray) Then MsgBox "array is empty! -> nothing to peek" Else Peek = myArray(LBound(myArray)) End If End Function Function Dequeue(myArray As Variant) As Variant If isArrayEmpty(myArray) Then MsgBox "array is empty! -> nothing to dequeue" Else Dequeue = myArray(LBound(myArray)) PackArray myArray End If End Function Sub Enqueue(myArray As Variant, arrayEl As Variant) Dim i As Long EnlargeArray myArray myArray(UBound(myArray)) = arrayEl End Sub Sub PackArray(myArray As Variant) Dim i As Long If LBound(myArray) < UBound(myArray) Then For i = LBound(myArray) + 1 To UBound(myArray) myArray(i - 1) = myArray(i) Next i ReDim Preserve myArray(LBound(myArray) To UBound(myArray) - 1) Else Clear myArray End If End Sub Sub EnlargeArray(myArray As Variant) Dim i As Long If isArrayEmpty(myArray) Then ReDim myArray(0 To 0) Else ReDim Preserve myArray(LBound(myArray) To UBound(myArray) + 1) End If End Sub Public Function isArrayEmpty(parArray As Variant) As Boolean 'http://stackoverflow.com/questions/10559804/vba-checking-for-empty-array 'assylias's solution 'Returns true if: ' - parArray is not an array ' - parArray is a dynamic array that has not been initialised (ReDim) ' - parArray is a dynamic array has been erased (Erase) If IsArray(parArray) = False Then isArrayEmpty = True On Error Resume Next If UBound(parArray) < LBound(parArray) Then isArrayEmpty = True Exit Function Else isArrayEmpty = False End If End Function then in your main sub you could go like this: Option Explicit Sub main() Dim arrayU As Variant Dim arrayX As Variant Dim arrayW As Variant Dim myVar As Variant Dim j As Integer, i As Integer, R As Integer Dim bed_in_use As Integer, Wait_L As Integer Dim arrayXi As Variant Const max_bed_in_use As Integer = 24 'best to declare a "magic" value as a constant and use "max_bed_in_use" in lieu of "24" in the rest of the code 'fill "queue" arrays With ActiveSheet arrayU = Application.Transpose(.Range(.cells(3, "U"), .cells(.Rows.Count, "U").End(xlUp))) 'fill arrayU arrayX = Application.Transpose(.Range(.cells(3, "X"), .cells(.Rows.Count, "X").End(xlUp))) 'fill arrayX arrayW = Application.Transpose(.Range(.cells(3, "W"), .cells(.Rows.Count, "W").End(xlUp))) 'fill arrayW End With 'some examples of using the "queue-array utilities" bed_in_use = Count(arrayU) 'get the number of elements in arrayU Enqueue arrayU, "foo" ' add an element in the arrayU queue, it'll be placed at the queue end Enqueue arrayU, "bar" ' add another element in the arrayU queue, it'll be placed at the queue end bed_in_use = Count(arrayU) 'get the update number of elements in arrayU Dequeue arrayU 'shorten the queue by removing its first element myVar = Dequeue(arrayU) 'shorten the queue by removing its first element and storing it in "myvar" bed_in_use = Count(arrayU) 'get the update number of elements in arrayU MsgBox Peek(arrayU) ' see what's the first element in the queue End Sub
Error sorting dictionary by ascending key value
I have a userform that contains a combobox that's populated from the unique items in a worksheet column. I'm trying to sort the keys that represent the items in the combobox in ascending order using the below code, but I'm getting an "Object variable or With block variable not set" error: Public Function funcSortKeysByLengthDesc(dctList As Object) As Object Dim curKey As Variant Dim key As Variant Dim itX As Integer Dim itY As Integer Dim arrTemp() As Variant Dim d As Object 'Only sort if more than one item in the dict If dctList.Count > 1 Then 'Populate the array ReDim arrTemp(dctList.Count) itX = 0 For Each curKey In dctList arrTemp(itX) = curKey itX = itX + 1 Next For itX = 0 To (dctList.Count - 2) For itY = (itX + 1) To (dctList.Count - 1) If arrTemp(itX) > arrTemp(itY) Then curKey = arrTemp(itY) arrTemp(itY) = arrTemp(itX) arrTemp(itX) = curKey End If Next Next 'Create the new dictionary Set d = CreateObject("Scripting.Dictionary") For itX = 0 To UBound(arrTemp) d.Add arrTemp(itX), dctList(itX) Next Set funcSortKeysByLengthDesc = d Else Set funcSortKeysByLengthDesc = dctList End If End Function
I'm not really sure why you're using a Dicionary for this task, but I've assumed it's required elsewhere in your project, so I've tried to dovetail mine into your existing code. If you are only putting sorted cells into a ComboBox then reading the cells into an array, removing duplicates and sorting that array, then populating the ComboBox would be simpler. There are plenty of examples of how to do each of these tasks on this site, so I won't reproduce them here. Here's the code for you: Sub RunMe() Dim ws As Worksheet Dim rCell As Range Dim dctItem As String Dim dctArray() As String Dim i As Integer Dim d As Object Dim v As Variant Set ws = ThisWorkbook.Worksheets("Sheet1") 'Code to poulate a few "C" cells ws.Cells(3, "C").Resize(10).Value = Application.Transpose(Array("Z", "Y", "X", "W", "W", "E", "D", "C", "B", "A")) UserForm1.Show False 'Clear the combobox UserForm1.cbNames.Clear 'Create the dictionary Set d = CreateObject("Scripting.Dictionary") For Each rCell In ws.Range("C3", ws.Cells(Rows.Count, "C").End(xlUp)) dctItem = CStr(rCell.Value2) If Not d.Exists(dctItem) Then d.Add dctItem, dctItem End If Next 'Convert the dictionary items to an array Debug.Print "PRE-SORT" ReDim dctArray(1 To d.Count) i = 1 For Each v In d.Items dctArray(i) = v i = i + 1 Debug.Print v Next 'Bubble sort the array dctArray = BubbleSort(dctArray) 'Populate the dictionary and combobox Debug.Print "POST-SORT" Set d = CreateObject("Scripting.Dictionary") For i = LBound(dctArray) To UBound(dctArray) d.Add dctArray(i), dctArray(i) UserForm1.cbNames.AddItem dctArray(i) Debug.Print dctArray(i) Next End Sub Private Function BubbleSort(tempArray As Variant) As Variant 'Uses Microsoft's version: https://support.microsoft.com/en-us/kb/133135 Dim temp As Variant Dim i As Integer Dim noExchanges As Integer ' Loop until no more "exchanges" are made. Do noExchanges = True ' Loop through each element in the array. For i = 1 To UBound(tempArray) - 1 ' If the element is greater than the element ' following it, exchange the two elements. If tempArray(i) > tempArray(i + 1) Then noExchanges = False temp = tempArray(i) tempArray(i) = tempArray(i + 1) tempArray(i + 1) = temp End If Next i Loop While Not (noExchanges) BubbleSort = tempArray End Function
Searching collections
I'm working with a rather large dataset (>100,000 rows) and trying to compare two lists to figure out which items in the new list are not already in the master list. In other words I want to find the new unique items. I have some VBA code that uses vlookup and arrays that works, but bombs out when the arrays get too big (~70,000). So I've turned to collections. However I'm having difficulty searching the collections using vlookup or match. Sub find_uniqueIDs() Dim a As Long Dim n As Long Dim m As Variant Dim oldnum As Long Dim oldIDs As Variant Dim oldcoll As New Collection Dim newnum As Long Dim newIDs As Variant Dim newcoll As New Collection oldnum = 75000 oldIDs = Range("A1", Range("A" & oldnum)) newnum = 45000 + 3 newIDs = Range("G3", Range("G" & newnum)) 'Using arrays to search, but bombs out when oldnum or newnum are ~70000 For n = 1 To newnum - 3 m = Application.VLookup(newIDs(n, 1), oldIDs, 1, False) If IsError(m) Then Range("E100000").End(xlUp).Offset(1, 0) = newIDs(n, 1) Next n 'Using collections to search For n = 1 To oldnum On Error Resume Next oldcoll.Add oldIDs(n, 1) On Error GoTo 0 Next n For m = 1 To newnum On Error Resume Next newcoll.Add newIDs(m, 1) On Error GoTo 0 Next m 'This bit of code doesn't work For a = 1 To newcoll.Count If Application.VLookup(newcoll(a), oldcoll, 1, False) = "#N/A" Then _ Range("E100000").End(xlUp).Offset(1, 0) = newcoll(a) Next a End Sub Any ideas how I can determine whether a particular item is in the master list using collections?
Here is a short sub demonstrating some of the scripting dictionary methods. Sub list_New_Unique() Dim dMASTER As Object, dNEW As Object, k As Variant Dim v As Long, vVALs() As Variant, vNEWs() As Variant Debug.Print "Start: " & Timer Set dMASTER = CreateObject("Scripting.Dictionary") Set dNEW = CreateObject("Scripting.Dictionary") dMASTER.comparemode = vbTextCompare dNEW.comparemode = vbTextCompare With Worksheets("Sheet7") vVALs = .Range("A2:A100000").Value2 vNEWs = .Range("C2:C100000").Value2 End With 'populate the dMASTER values For v = LBound(vVALs, 1) To UBound(vVALs, 1) dMASTER.Add Key:=vVALs(v, 1), Item:=vVALs(v, 1) Next v 'only populate dNEW with items not found in dMASTER For v = LBound(vNEWs, 1) To UBound(vNEWs, 1) If Not dMASTER.exists(vNEWs(v, 1)) Then If Not dNEW.exists(vNEWs(v, 1)) Then _ dNEW.Add Key:=vNEWs(v, 1), Item:=vNEWs(v, 1) End If Next v Debug.Print dNEW.Count For Each k In dNEW.keys 'Debug.Print k Next k Debug.Print "End: " & Timer dNEW.RemoveAll: Set dNEW = Nothing dMASTER.RemoveAll: Set dMASTER = Nothing End Sub With 99,999 unique entries in A2:A100000 and 89747 random entries in C2:C89747, this found 70,087 unique new entries not found in A2:A100000 in 9.87 seconds.
I would do it like this: Sub test() Dim newRow As Long, oldRow As Long Dim x As Long, Dim y As Long Dim checker As Boolean With ActiveSheet newRow = .Cells(.Rows.Count,7).End(xlUp).Row oldRow = .Cells(.Rows.Count,1).End(xlUp).Row checker = True for y = 1 To oldRow for x = 1 To newRow If .Cells(y,1).Value = .Cells(x,7).Value Then checker = False Exit For End If Next If checker Then Range("E10000").End(xlUp).Offset(1,0).Value = .Cells(y,1).Value End If checker = True Next End With End Sub
VLookup is a worksheet function, not a regular VBA function, thus it's for searching in Ranges, not Collections. Syntax: VLOOKUP (lookup_value, table_array, col_index_num, [range_lookup]) [...] table_array (required): the range of cells in which the VLOOKUP will search for the lookup_value and the return value. In order to search in other VBA data structures like arrays, collections etc you'll have to figure out some other way and maybe implement it manually.
While #Jeeped suggestion of a Scripting.Dictionary object might be the best one, you could also try using the Filter() function applied to your array.