Initialize a list (or array) of a variable length - vba

I know that we could declare a list of a fixed length in VBA by, eg, Dim result as Variant followed by ReDim result(1 to 10). But I have difficulty in declaring a list of a variable length in VBA.
For instance, given a list arr, I would like to create another list result that removes all the empty and 0. I have written a code as follows:
Dim result As Variant
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i) <> "" And arr(i) <> 0 Then
ReDim Preserve result(1 To UBound(result, 1) + 1)
result(UBound(result, 1)) = arr(i)
End If
Next i
This code does not run, because the length of result is not well initialised; and ReDim Preserve result(1 To UBound(result, 1) + 1) raises an error in the very beginning.
Does anyone know how to fix this code?
PS: If ReDim inside a loop causes a bad performance, could anyone suggest a better way to restructure the code?

Examining your code it looks like you are filtering arr to get result where result excludes values that are not "" or 0. If this is the case then arrays are the wrong approach. You should consider using a Collection, ArrayList or Scripting.Dictionary, preferably one of the latter two.
You can use ArrayLists in VBA if you add a reference to msCorlib.
You can use Scripting.Dictionary if you create a late bound object 'CreateObj("Scripting.DIctionary") or add a reference to the microsoft scripting runtime for 'new Scripting.DIctionary'.
The reason I would recommend ArrayList or Scripting.Dictionary over Collection is that both of these objects can return arrays of the items they hold, which can be very convenient if you are working with Excel.
The Arraylist version of you code would look like
Dim myAL as ArrayList
Set MyAL = New ArrayList
Dim myItem as variant
For each myItem in arr
If myItem<>"" and myItem<>0 then myAl.add myitem
Next
'If you need an array then
dim myResult as variant
myResult = myAl.toarray
The scripting dictionary version looks similar but requires a key for each item. In you case an autokey generated by using mySD.count would be fine.
Dim mySD as Scripting.Dictionary
Set MySL = New Scripting.Dictionary
Dim myItem as variant
For each myItem in arr
If myItem<>"" and myItem<>0 then mySD.add mySD.count,myitem
Next
'If you need an array then
dim myResult as variant
myresult = mySD.Items
FInally, if you know in advance that arr is composed entirely of string values, you could look at the VBA Filter function for onedimensional string arrays.

You can declare the largest possible size of the array (in this case, It would be UBound(arr, 1) assuming that LBound(arr, 1) is 1) outside of the loop first then resize it again at the end of the loop:
Dim result As Variant
ReDim result(1 to Ubound(arr, 1)) As Variant
Dim resultCount As Long
Dim i As Long
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i) <> "" And arr(i) <> 0 Then
resultCount = resultCount + 1
result(resultCount) = arr(i)
End if
Next i
ReDim Preserve result(1 to resultCount) As Variant

How about you find the first value and then do
result = Array(arr(I))?
I just checked in the immediate pannel ?UBound(Array()) gives -1, so
Dim result As Variant
result = Array()
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i) <> "" And arr(i) <> 0 Then
ReDim Preserve result(0 To UBound(result, 1) + 1)
result(UBound(result, 1)) = arr(i)
End If
Next i
should work with a 0-based "result" array or
Dim result As Variant
ReDim result(1 To 1)
For i = LBound(arr, 1) To UBound(arr, 1)
If arr(i) <> "" And arr(i) <> 0 Then
ReDim Preserve result(1 To UBound(result, 1) + 1)
result(UBound(result, 1)) = arr(i)
End If
Next i

Related

Adding a dictionary to array

I'd like to go through list in Excel and assign the values to dictionary. The the dictionary will be put into list. This will happen until the cells in the column are blank. As a result of entire function, the array of dictionaries will be returned.
Function CreateArrayofDicts()
Dim catlist As Variant
Dim catparams() As Variant
Dim ChartParamsDict As Dictionary
Dim k As Long
catlist = ArrayProblemsCat()
i = 1
Do Until IsEmpty(Cells(i, 1))
Set ChartParamsDict = New Scripting.Dictionary
For j = 0 To UBound(catlist)
Debug.Print Cells(i, 1)
If Cells(i, 1) = catlist(j) Then
Debug.Print 5
ChartParamsDict.Add Key:="Cells(1,2)", Item:=Cells(i, 2)
ChartParamsDict.Add Key:="Cells(1,3)", Item:=Cells(i, 3)
ChartParamsDict.Add Key:="Cells(1,4)", Item:=Cells(i, 4)
ReDim Preserve catparams(k)
catparams(k) = ChartParamsDict ' issues is here
k = k + 1
Debug.Print ChartParamsDict
End If
Next j
i = i + 1
Loop
CreateArrayofDicts = catparams
End Function
Your are missing Set on the problem line.
Set catparams(k) = ChartParamsDict

Sorting by column using character in middle of each cell, without helper column

Is it possible to sort a range by a column, but sort using a single character in the middle of the string in each cell?
So column looks like this:
red(7)
blue(4)
orange(9)
green(2)
etc..
I want to sort it using the number within the brackets.
My current code sorts the columns alphabetically:
With sheetSUMMARY
.Range(.Cells(summaryFirstRow, summaryReForenameCol)), _
.Cells(summaryLastRow, summaryReColourCol))). _
Sort _
key1:=.Range(.Cells(summaryFirstRow, summaryReColourCol)), _
.Cells(summaryLastRow, summaryReColourCol))), _
order1:=xlAscending, _
Header:=xlNo
End With
So it looks like this:
blue(4)
green(2)
orange(9)
red(7)
Without making a helper column in excel (which extracts the numbers), is it possible to sort it like this purely programatically? (I haven't really got space for a helper column at this stage)
green(2)
blue(4)
red(7)
orange(9)
You can use a Dictionary to store your values and their corresponding numbers and then there are a number of sorting methods. I opted to use an ArrayList to do the sorting rather than writing a bespoke sorting function.
Public Sub SortByNumber()
Dim arrayList As Object, inputDictionary As Object, outputDictionary As Object 'late binding so you can drop the code in easily
Dim rng As Range, r As Range
Dim num As Double
Dim v As Variant
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:A4")
Set arrayList = CreateObject("System.Collections.ArrayList")
Set inputDictionary = CreateObject("Scripting.Dictionary")
Set outputDictionary = CreateObject("Scripting.Dictionary")
'put current values into dictionary and arraylist
For Each r In rng
num = CLng(Mid(r.Value, InStr(r.Value, "(") + 1, Len(r.Value) - InStr(r.Value, "(") - 1))
Do While inputDictionary.exists(num) 'avoid errors with duplicates numbers (see comments)
num = num + 0.00000001
Loop
inputDictionary.Add Item:=r.Value, Key:=num
arrayList.Add num
Next r
arrayList.Sort
'use sorted arraylist to determine order of items in output dictionary
For Each v In arrayList.toarray
outputDictionary.Add Item:=v, Key:=inputDictionary.Item(v)
Next v
'output values to the next column -- just remove the offset to overwrite original values
rng.Offset(0, 1).Value = WorksheetFunction.Transpose(outputDictionary.keys())
End Sub
The result looks like this:
You can do something interesting, if you really do not want to add a helper column. Pretty much the following:
let's say your inputRange is Range("A1:A4")
declare a variant virtualRange, which would be a bit of a tricky - it would take the values of the inputRange and the next column:
virtualRange = Union(inputRange, inputRange.Offset(0, 1)).Value
then loop through your inputRange and assign the cell value to the second dimension of the virtualRange. It should pretty much look like this in the local window:
Now the funny part - pass the virtualRange to the SortDataBySecondValue and it will return the virtualRange sorted. Here is a really important point - if you pass the virtualRange with parenthesis, like this SortDataBySecondValue (virtualRange) nothing useful would happen - the parenthesis overrule the ByRef argument in SortDataBySecondValue() and the virtualRange would remain untact.
At the end you have your virtualRange sorted and you have to pass its values correctly to the inputRange. This is achievable with a simple loop:
For Each myCell In inputRange
Dim cnt As Long
cnt = cnt + 1
myCell = virtualRange(cnt, 1)
Next myCell
Now the inputRange is sorted as expected:
The whole code is here:
Option Explicit
Public Sub TestMe()
Dim inputRange As Range
Dim myCell As Range
Dim virtualRange As Variant
Set inputRange = Range("A1:A4")
virtualRange = Union(inputRange, inputRange.Offset(0, 1)).Value
For Each myCell In inputRange.Columns(1).Cells
virtualRange(myCell.Row, 2) = locateNumber(myCell)
Next myCell
SortDataBySecondValue virtualRange
For Each myCell In inputRange
Dim cnt As Long
cnt = cnt + 1
myCell = virtualRange(cnt, 1)
Next myCell
End Sub
Public Function locateNumber(ByVal s As String) As Long
Dim startIndex As Long
Dim endIndex As Long
startIndex = InStr(1, s, "(") + 1
endIndex = InStr(1, s, ")")
locateNumber = Mid(s, startIndex, endIndex - startIndex)
End Function
Sub SortDataBySecondValue(ByRef Data As Variant)
Dim i As Long
Dim j As Long
Dim temp As Variant
Dim sortBy As Long: sortBy = 2
ReDim temp(UBound(Data) - 1, sortBy)
For i = LBound(Data) To UBound(Data)
For j = i To UBound(Data)
If Data(i, sortBy) > Data(j, sortBy) Then
temp(i, 1) = Data(i, 1)
temp(i, sortBy) = Data(i, sortBy)
Data(i, 1) = Data(j, 1)
Data(i, sortBy) = Data(j, sortBy)
Data(j, 1) = temp(i, 1)
Data(j, sortBy) = temp(i, sortBy)
End If
Next j
Next i
End Sub
Try this:
Sub OrderByColumn()
Dim i As Long, tempColumn As Long, colorColumn As Long, color As String
'get table to variable
Dim tableToOrder As Range
'here ypou have to specify your own range!!
Set tableToOrder = Range("A1:C5")
colorColumn = tableToOrder.Column
tempColumn = colorColumn + tableToOrder.Columns.Count
'insert new column at the end of the table and populate with extracted numbers
Columns(tempColumn).Insert
For i = tableToOrder.Row To (tableToOrder.Rows.Count + tableToOrder.Row - 1)
color = Cells(i, colorColumn).Value
Cells(i, tempColumn).Value = Mid(color, InStr(1, color, "(") + 1, InStr(1, color, ")") - InStr(1, color, "(") - 1)
Next
i = i - 1 'now i points to last row in range
'order whole table accordingly to temporary column
Range(Cells(tableToOrder.Row, tableToOrder.Column), Cells(i, tempColumn)).Sort Key1:=Range(Cells(tableToOrder.Row, tempColumn), Cells(i, tempColumn))
'delete column
Columns(tempColumn).Delete
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")

Excel VBA Redim Preserve Out of range

i am trying to redim and preserve my array but it keeps coming up with an error that says it is out of range i do not know why?
Dim FirstArray(0) As Variant
Dim SecondArray(0) As Variant
Dim ArrSize As Variant
row = 9
ArrSize = 1
Do While Not Cells(row, 3).Comment Is Nothing
FirstArray(ArrSize - 1) = Cells(row, 3).Value
ReDim Preserve FirstArray(ArrSize)
ArrSize = ArrSize + 1
row = row + 1
Loop
It should be:
Dim FirstArray() as Variant
Dim SecondArray() As Variant
I made the same mistake initially
To declare a variable array, you don't give it an initial size.
Also, make sure you ReDim it to 1 before you assign its 0 index a value.
ReDim Preserve FirstArray(ArrSize)
FirstArray(ArrSize - 1) = Cells(row, 3).Value
Hope this helps!
You could also use an ArrayList object which saves having to ReDim things. Also has added functionality (see here for example)
Dim FirstArray As Object
Set FirstArray = CreateObject("System.Collections.ArrayList")
row = 9
Do While Not Cells(row, 3).Comment Is Nothing
FirstArray.Add Cells(row, 3).Value
row = row + 1
Loop

How to remove duplicate values from 2 columns in excel using vba

I am new to Excel VBA Programming. I have one excel sheet with two columns and each column has some email adresses separated by ##. like
ColumA
aa#yahoo.com##bb#yahoo.com##cc#yahoo.com
x#.com##y#y.com
ColumnB
zz#yahoo.com##aa#yahoo.com
aa#yahoo.com
As you can see that both column has two rows, I need 3rd column that should contain all the unique values like
ColumnC
aa#yahoo.com##bb#yahoo.com##cc#yahoo.com#zz#yahoo.com
x#.com##y#y.com##aa#yahoo.com
Thanks
Something like this with variant arrays and a dictionary is an efficient process of getting your desired outcome
[updated to remove delimiter at front of string, code is flexible on delimiter length]
SO seems to have removed the ability to upload image so my picture has fallen off ....
Sub GetUniques()
Dim strDelim As String
Dim X
Dim Y
Dim objDic As Object
Dim lngRow As Long
Dim lngRow2 As Long
strDelim = "##"
Set objDic = CreateObject("scripting.dictionary")
X = Range([a1], Cells(Rows.Count, "B").End(xlUp)).Value2
For lngRow = 1 To UBound(X, 1)
X(lngRow, 1) = X(lngRow, 1) & strDelim & X(lngRow, 2)
Y = Split(X(lngRow, 1), strDelim)
X(lngRow, 1) = vbNullString
For lngRow2 = 0 To UBound(Y, 1)
If Not objDic.exists(lngRow & Y(lngRow2)) Then
X(lngRow, 1) = X(lngRow, 1) & (strDelim & Y(lngRow2))
objDic.Add (lngRow & Y(lngRow2)), 1
End If
Next lngRow2
If Len(X(lngRow, 1)) > Len(strDelim) Then X(lngRow, 1) = Right(X(lngRow, 1), Len(X(lngRow, 1)) - Len(strDelim))
Next lngRow
[c1].Resize(UBound(X, 1), 1).Value2 = X
End Sub
Here's my take. How it works:
Dump columnA and B into a variant array
Combine each row, split into an array of emails, then weed out dupes with a dictionary.
Combine unique list into a single string and store in a new array
Transpose the new array onto column C.
Sub JoinAndUnique()
Application.ScreenUpdating = False
Dim varray As Variant, newArray As Variant
Dim i As Long, lastRow As Long
Dim temp As Variant, email As Variant
Dim newString As String, seperator As String
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
seperator = "##"
lastRow = range("A" & Rows.count).End(xlUp).Row
varray = range("A1:B" & lastRow).Value
ReDim newArray(1 To UBound(varray, 1))
On Error Resume Next
For i = 1 To UBound(varray, 1)
temp = Split(varray(i, 1) & seperator & varray(i, 2), seperator)
For Each email In temp
If Not dict.exists(email) Then
dict.Add email, 1
newString = newString & (seperator & email)
End If
Next
newArray(i) = Mid$(newString, 3)
dict.RemoveAll
newString = vbNullString
Next
range("C1").Resize(UBound(newArray)).Value = Application.Transpose(newArray)
Application.ScreenUpdating = True
End Sub
Note:
It's fairly similar to brettdj's answer, but there are a few differences worth mentioning:
I used more meaninful names for variables (for readability and to make it easier to edit)
I do clean up of the "##" at the start of the sentence
I use a new array rather than overwrite the values of an existing one
I choose to clear the dictionary after each cell
I choose to use "on error resume next" and just dump entries into the dictionary instead of checking if they exist or not (personal preference, makes no major difference)
The easiest way to do this would be to use the dictionary object, split function, and join function. Of course, you don't need to use those exact ones, but give it a try and see what you get.