Subscript out of range during array manipulation - excel

I've written the below function intended to take an input array, delete the duplicates and return an array of unique values. I've looked at other functions open source that are similar but could not get them to work either. Watching both input array and the function arrays, Arr and ArrCopy, they have the correct number and value for each index. Any ideas why I'm getting an out of range error?
Public Function getUnique(Arr As Variant) As Variant
Dim ArrCopy As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
'copies input array, loops through copy and clears dupates
ArrCopy = Arr
For i = LBound(Arr) To UBound(Arr)
For j = LBound(ArrCopy) To UBound(ArrCopy)
If Arr(i) = ArrCopy(j) And i <> j Then
ArrCopy(j).Clear
End If
Next j
Next i
'clears array, loops through copy and puts nonzero values back in Arr
Arr.Clear
counter = 0
For i = LBound(ArrCopy) To UBound(ArrCopy)
If ArrCopy(i) <> "" Then
ReDim Preserve Arr(0 To counter)
Arr(counter) = ArrCopy(i)
counter = counter + 1
End If
Next i
'returns unique values
getUnique = Arr
End Function
Update: This is how the array gets loaded. From FaneDuru's comment, I see in the watch table that the input array is actually 2D, so that's why I'm getting an out of range error....
'removes blanks from AO
wks.AutoFilterMode = False
wks.Range("A1:BO" & lastrow).AutoFilter Field:=41, Criteria1:="<>", Operator:=xlFilterValues
Set rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'loads SNs into array
Erase serialNum
serialNum = rng.Value
Update 2:
This has me a lot closer. Using the 2d approach This will set all of the repeats to 0. Then I call a delete element sub I found (Deleting Elements in an Array if Element is a Certain value VBA). I am modifying the original to work with 2D array. I am getting a subscript out of range error on my Redim Preserve line within the DeleteElementAt() sub.
Public Function GetUnique(Arr As Variant) As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
For i = LBound(Arr) To UBound(Arr)
For j = LBound(Arr) To UBound(Arr)
If i <> j And Arr(i, 1) = Arr(j, 1) Then
Arr(j, 1) = "0"
End If
Next j
Next i
counter = 0
For i = LBound(Arr) To UBound(Arr)
If Arr(i, 1) = "0" Then
Call DeleteElementAt(i, Arr)
ReDim Preserve Arr(0 To UBound(Arr))
End If
Next i
GetUnique = Arr
End Function
Public Sub DeleteElementAt(ByVal index As Integer, ByRef Arr As Variant)
Dim i As Integer
' Move all element back one position
For i = index + 1 To UBound(Arr)
Arr(index, 1) = Arr(i, 1)
Next i
' Shrink the array by one, removing the last one
'ERROR HERE
ReDim Preserve Arr(LBound(Arr) To UBound(Arr) - 1, 1)
End Sub

Return the Unique Values From a Range in an Array
Option Explicit
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim rg As Range: Set rg = ws.Range("A2:J21")
Dim Data As Variant: Data = GetRange(rg)
Dim Arr As Variant: Arr = ArrUniqueData(Data)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
' Dim n As Long
' For n = 0 To UBound(Arr)
' Debug.Print Arr(n)
' Next n
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the unique values from a 2D array
' to a 1D zero-based array, excluding error values and blanks.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrUniqueData( _
Data As Variant, _
Optional ByVal CompareMethod As VbCompareMethod = vbTextCompare) _
As Variant
Const ProcName As String = "ArrUniqueDatae"
On Error GoTo ClearError
Dim cLower As Long: cLower = LBound(Data, 2)
Dim cUpper As Long: cUpper = UBound(Data, 2)
Dim Key As Variant
Dim r As Long
Dim C As Long
With CreateObject("Scripting.Dictionary")
.CompareMode = CompareMethod
For r = LBound(Data, 1) To UBound(Data, 1)
For C = cLower To cUpper
Key = Data(r, C)
If Not IsError(Key) Then ' exclude error values
If Len(Key) > 0 Then ' exclude blanks
.Item(Key) = Empty
End If
End If
Next C
Next r
If .Count = 0 Then Exit Function
ArrUniqueData = .Keys
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
EDIT
This will continue your sub using the (SpecialCells) filtered one-column range. You still need the previous procedures (except the Test procedure) and there is a new function below.
' This is your procedure!
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: ...
' Calls: GetFilteredColumn
' GetRange
' ArrUniqueData
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub YourProcedure()
' ... whatever
Set Rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'Erase serialNum ' you don't need to erase
serialNum = GetFilteredColumn(Rng)
Dim Arr As Variant: Arr = ArrUniqueData(serialNum)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the filtered values of a column range
' in a 2D one-based array.
' Calls: GetRange.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetFilteredColumn( _
ByVal FilteredColumnRange As Range) _
As Variant
Const ProcName As String = "GetFilteredColumn"
On Error GoTo ClearError
With FilteredColumnRange
Dim aCount As Long: aCount = .Areas.Count
Dim aData As Variant: ReDim aData(1 To aCount)
Dim arg As Range
Dim a As Long
For Each arg In .Areas
a = a + 1
aData(a) = GetRange(arg)
Next arg
Dim dData As Variant: ReDim dData(1 To .Cells.Count, 1 To 1)
Dim sr As Long
Dim dr As Long
For a = 1 To aCount
For sr = 1 To UBound(aData(a), 1)
dr = dr + 1
dData(dr, 1) = aData(a)(sr, 1)
Next sr
Next a
GetFilteredColumn = dData
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

When you assign the values of an Excel range to a variant in VBA you always get a 2D array even if your range is a single column or row i.e. you get an array that is dimensioned as (1 to X,1 to 1). To get an array with dimensions (1 to X) you need to encapsulate the 'get values' code in a worksheetFunction.Transpose() call.
Assuming you have got your array into a 1D form you can then use either an ArrayList or Scripting.Dictionary to simplify compiling unique values. No need to get messyt with array indeces at all.
This is the ArrayList Version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("System.collections.Arraylist")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.Contains(myItem) Then
myList.Add myItem
End If
End If
Next
getUnique = myList.toarray
End Function
This is the Scripting.Dictionary version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.exists(myItem) Then
myList.Add myList.Count, myItem
End If
End If
Next
getUnique = myList.Items
End Function

Related

Create an array of the location of all the cells in the first column of a selection in VBA

I would like to create an array that has the actual cell locations of the first column of a selection. For example if I've selected cells E26:I31, I would like to produce an array with the following: ar = Array("E26", "E27", "E28", "E29", "E30", "E31"). I imagine there may be a quick way to do this but I haven't quite figured it out yet. Thanks! Here is an example of the array that would work for my code vs what using columns(1) would store:
In order to do this you need to use the ReDim statement. Try this:
Dim selected As Range
Dim myArray As Variant
Set selected = Selection.Columns(1)
ReDim myArray(selected.Rows.Count)
Dim i As Integer
For i = 1 To selected.Rows.Count
myArray(i) = selected.Cells(i).Address
Next i
Cell Addresses in an Array
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the cell addresses of the first column
' (of the first area) of a range to an array.
' Calls: ArrFirstColumnAddresses
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub ArrFirstColumnAddressesTEST()
If Not TypeOf Selection Is Range Then
MsgBox "The selection is not a range.", vbCritical
Exit Sub
End If
Dim sArr() As String: sArr = ArrFirstColumnAddresses(Selection)
' Do something, e.g.:
Debug.Print Join(sArr, " ") ' row
Debug.Print Join(sArr, vbLf) ' column
Debug.Print "[LB=" & LBound(sArr) & ",UB=" & UBound(sArr) & "]"
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the cell addresses of the first column
' (of the first area) of a range in an array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrFirstColumnAddresses( _
ByVal SourceRange As Range) _
As String()
Const ProcName As String = "ArrFirstColumnAddresses"
Dim AnErrorHasOccurred As Boolean
On Error GoTo ClearError
Dim rg As Range: Set rg = SourceRange.Areas(1).Columns(1)
Dim ColString As String: ColString = Split(rg.Cells(1).Address, "$")(1)
Dim FirstRow As Long: FirstRow = rg.Row
Dim rCount As Long: rCount = rg.Rows.Count
Dim sArr() As String: ReDim sArr(0 To rCount - 1)
Dim r As Long
For r = FirstRow To FirstRow + rCount - 1
sArr(r - FirstRow) = ColString & CStr(r)
Next r
ProcExit:
If AnErrorHasOccurred Then
ArrFirstColumnAddresses = Split("")
Else
ArrFirstColumnAddresses = sArr
End If
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
AnErrorHasOccurred = True
Resume ProcExit
End Function

Count and list all values

I'm trying to loop through cells of a specific column, find new values in those cells, count how many times a specific value is found and return the values along with the number of times it appeared.
The values I am looking at are all text.
Something like this:
This is the code I found. I get a compile error
ByRef arguement type mismatch
Function findValues() As Scripting.Dictionary
Dim cellValue
Dim dict As New Scripting.Dictionary
For iRow = 2 To g_totalRow
cellValue = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
If dict.Exists(cellValue) Then
dict.Item(cellValue) = dict.Item(cellValue) + 1
Else
dict.Item(cellValue) = 1
End If
Next iRow
Set findValues = dict
End Function
Sub displayValues(dict As Scripting.Dictionary)
Dim i
Dim value
Dim valueCount
For i = 1 To dict.count
valueCount = dict.Items(i)
value = dict.Keys(i)
ActiveWorkbook.Sheets(sheetName).Cells(i, 3).Text = value
ActiveWorkbook.Sheets(sheetName).Cells(i, 4).Text = valueCount
Next i
End Sub
Sub RunAndDisplay()
Dim dict
Set dict = findValues
displayValues dict
End Sub
Write Unique Column Values With Count
Here's a version more customized to your actual case.
Adjust the values in the constants section.
You only need to replace the worksheet names (sName and dName) with your actual worksheet (tab) name(s).
You can easily write the result to another worksheet by changing the dName constant.
There is no need for a reference to the Microsoft Scripting Runtime so remove it from the workbook (VBE>Tools>References).
Option Explicit
Sub WriteUniqueWithCount()
Const ProcName As String = "WriteUniqueWithCount"
On Error GoTo ClearError
' Source
Const sName As String = "Sheet1"
Const sfCellAddress As String = "A2"
' Destination
Const dName As String = "Sheet1"
Const dfCellAddress As String = "C2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
If sws.AutoFilterMode Then sws.AutoFilterMode = False
Dim sfCell As Range: Set sfCell = sws.Range(sfCellAddress)
Dim scrg As Range: Set scrg = RefColumn(sfCell)
If scrg Is Nothing Then Exit Sub ' no data in column
Dim Data As Variant: Data = GetRange(scrg)
Dim dict As Object: Set dict = DictColumnCount(Data)
If dict Is Nothing Then Exit Sub ' only error values and blanks
Data = GetDict(dict) ' 2 columns: keys (values) and items (count)
Set dict = Nothing
Dim rCount As Long: rCount = UBound(Data, 1)
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
If dws.AutoFilterMode Then dws.AutoFilterMode = False
Dim dfCell As Range: Set dfCell = dws.Range(dfCellAddress)
With dfCell.Resize(, 2) ' first row
.Resize(rCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - rCount + 1).Offset(rCount).Clear
End With
MsgBox "Unique values and their count are written.", vbInformation, ProcName
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the one-column range from the first cell
' of a range ('FirstCell') to the bottom-most non-empty cell
' of the first cell's worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
Const ProcName As String = "RefColumn"
On Error GoTo ClearError
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the unique values and their count from a column
' ('ColumnIndex') of a 2D array ('Data') in the keys and items
' of a dictionary.
' Remarks: Error values and blanks are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function DictColumnCount( _
ByVal Data As Variant, _
Optional ByVal ColumnIndex As Variant) _
As Object
Const ProcName As String = "DictColumnCount"
On Error GoTo ClearError
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim c As Long
If IsMissing(ColumnIndex) Then
c = LBound(Data, 2)
Else
c = CLng(ColumnIndex)
End If
Dim Key As Variant
Dim r As Long
For r = LBound(Data, 1) To UBound(Data, 1)
Key = Data(r, c)
If Not IsError(Key) Then
If Len(CStr(Key)) > 0 Then
dict(Key) = dict(Key) + 1
End If
End If
Next r
If dict.Count = 0 Then Exit Function
Set DictColumnCount = dict
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values from a dictionary in a 2D one-based array.
' Remarks: F, F, F - returns the keys and items in two columns.
' F, F, T - returns the items and keys in two columns.
' F, T, F - returns the keys in a column.
' F, T, T - returns the items in a column.
' T, F, F - returns the keys and items in two rows.
' T, F, T - returns the items and keys in two rows.
' T, T, F - returns the keys in a row.
' T, T, T - returns the items in a row.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetDict( _
ByVal sDict As Object, _
Optional ByVal Horizontal As Boolean = False, _
Optional ByVal FirstOnly As Boolean = False, _
Optional ByVal Flip As Boolean = False) _
As Variant
Const ProcName As String = "GetDict"
On Error GoTo ClearError
Dim sCount As Long: sCount = sDict.Count
If sCount = 0 Then Exit Function
Dim Data As Variant
Dim Key As Variant
Dim i As Long
If Not Horizontal Then
If Not FirstOnly Then
ReDim Data(1 To sCount, 1 To 2)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = Key
Data(i, 2) = sDict(Key)
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = sDict(Key)
Data(i, 2) = Key
Next Key
End If
Else
ReDim Data(1 To sCount, 1 To 1)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = Key
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = sDict(Key)
Next Key
End If
End If
Else
If Not FirstOnly Then
ReDim Data(1 To 2, 1 To sCount)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = Key
Data(2, i) = sDict(Key)
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = sDict(Key)
Data(2, i) = Key
Next Key
End If
Else
ReDim Data(1 To 1, 1 To sCount)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = Key
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = sDict(Key)
Next Key
End If
End If
End If
GetDict = Data
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

VBA transponded Values not getting recognized by Excel

So I have a VBA macro that lets me use Cells like a Userform.
What it basically does is, that it takes the Values of the defined Range("E2:E11")
and transposes them to the next BlankRow.
So with that the User can easily generate a Matrix that ranges from C16:L100
The VBA Code looks like this:
Sub NeuesKFZ()
Dim sh As Worksheet, arr, lastERow As Long, matchCel As Range
Set sh = ActiveSheet
arr = sh.Range("E2:E11").Value
lastERow = sh.Range("C" & sh.Rows.Count).End(xlUp).Row + 1
If Range("E2") = "" Then
MsgBox "Wählen Sie ein KFZ aus!"
Range("E2").Select
Exit Sub
End If
If lastERow < 16 Then lastERow = 16
'check if the range has not been already copied:
Set matchCel = sh.Range("C16:C" & lastERow - 1).Find(WHAT:=sh.Range("E2").Value, LookIn:=xlValues, Lookat:=xlWhole, MatchCase:=False)
If Not matchCel Is Nothing Then
If MsgBox(sh.Range("E2").Value & " Existiert bereits " & vbCrLf & "Sollen die Daten aktualisiert werden?", vbYesNo) = vbYes Then
sh.Range("C" & matchCel.Row).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
End If
sh.Range("E2:E11").ClearContents
Exit Sub
End If
sh.Range("C" & lastERow).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
sh.Range("E2:E11").ClearContents
End Sub
My Problem is now that excel doesn't seem to know what kind of values are getting transposed to the new cells.
In my case I'm working with Dates and I want to know if a date is within the next three months.
But Excel doesn't recognize that it is working with date values.
Trying to format or delete all formatting doesn't help.
And when I'm trying to use this formula:
=if(and(C16>=$AA16$,C16<=$AB$17),TRUE,FALSE)
Some explenation:
C16 is the value that got transposed by the Macro.
AA16 and AB16 are the starting date and end date.
TRUE and FALSE are just to give me feedback if it works or not.
It just gives me FALSE all the time.
Is there a way to get the Date transposed so Excel still knows that its a Date?
Or Maybe force Excel to handle those values as dates.
Transpose Column
I think I read somewhere that besides the size limitation, Transpose 'doesn't like' dates. Anyway, make the following corrections to your code and copy the function to a standard module of your workbook.
Corrections
Replace arr = sh.Range("E2:E11").Value
with arr = GetRowData(sh.Range("E2:E11")).
Replace sh.Range("C" & matchCel.Row).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
with sh.Range("C" & matchCel.Row).Resize(1, UBound(arr, 2)).Value = arr.
Replace sh.Range("C" & lastERow).Resize(1, UBound(arr)).Value = Application.Transpose(arr)
with sh.Range("C" & lastERow).Resize(1, UBound(arr, 2)).Value = arr
The Function
Function GetRowData( _
ByVal ColumnRange As Range) _
As Variant
If ColumnRange Is Nothing Then Exit Function
With ColumnRange.Columns(1)
Dim rCount As Long: rCount = .Rows.Count
Dim rData As Variant
If rCount = 1 Then
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = .Value
Else
Dim cData As Variant: cData = .Value
ReDim rData(1 To 1, 1 To rCount)
Dim r As Long
For r = 1 To rCount
rData(1, r) = cData(r, 1)
Next r
End If
GetRowData = rData
End With
End Function
EDIT
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a 2D one-based one-row array containing the values
' from a one-column range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRowFromColumn( _
ByVal ColumnRange As Range) _
As Variant
If ColumnRange Is Nothing Then Exit Function
With ColumnRange.Columns(1)
Dim rCount As Long: rCount = .Rows.Count
Dim rData As Variant
If rCount = 1 Then
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = .Value
Else
Dim cData As Variant: cData = .Value
ReDim rData(1 To 1, 1 To rCount)
Dim r As Long
For r = 1 To rCount
rData(1, r) = cData(r, 1)
Next r
End If
GetRowFromColumn = rData
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a 2D one-based one-column array containing the values
' from a one-row range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetColumnFromRow( _
ByVal RowRange As Range) _
As Variant
If RowRange Is Nothing Then Exit Function
With RowRange.Rows(1)
Dim cCount As Long: cCount = .Columns.Count
Dim cData As Variant
If cCount = 1 Then
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = .Value
Else
Dim rData As Variant: rData = .Value
ReDim cData(1 To cCount, 1 To 1)
Dim c As Long
For c = 1 To cCount
cData(c, 1) = rData(1, c)
Next c
End If
GetColumnFromRow = cData
End With
End Function

Single column values ends up in multi dimension array

I am populating a array with values from part of a column (range). The resulting array is multidimensional - but it should be one dimensional. I want to get just Emp ID values into the array:
I have tried this :
Sub Test()
Dim colPostionNumber As Integer
Dim lastRow As Integer
Dim ws As Worksheet
Dim positionNumberArray As Variant
Set ws = ActiveSheet
With ActiveWorkbook.Sheets("Sheet 1")
colPositionNumber = Application.WorksheetFunction.Match("Emp ID", ws.Rows(5), 0)
lastRow = ws.UsedRange.SpecialCells(xlCellTypeLastCell).row
positionNumberArray = .Range(Cells(5, colPositionNumber), Cells(lastRow, colPositionNumber)).Value
End With
End Sub
But the resulting array is two dimensional
I tried reDim but that didn't work. How do I do this with a one dimensional array?
Write One-Column 2D Array to 1D Array
To get a zero-based 1D array, you will have to loop.
Sub Test()
Dim colPositionNumber As Long
Dim lastRow As Long
Dim ws As Worksheet
Dim Data As Variant
Dim positionNumberArray As Variant
Set ws = ActiveSheet
With ActiveWorkbook.Sheets("Sheet 1")
colPositionNumber = Application.Match("Emp ID", ws.Rows(5), 0)
lastRow = ws.UsedRange.SpecialCells(xlCellTypeLastCell).Row
Data = .Range(.Cells(5, colPositionNumber), _
.Cells(lastRow, colPositionNumber)).Value
ReDim positionNumberArray(UBound(Data, 1) - 1)
Dim n As Long
For n = 1 To UBound(Data, 1)
positionNumberArray(n - 1) = Data(n, 1)
Next n
End With
End Sub
Using Application.Transpose
The following procedures show how to write a one-column or a one-row range to a one-based 1D array:
Sub testATColumn()
Dim rg As Range: Set rg = Range("A1:A5")
Dim arr As Variant: arr = Application.Transpose(rg.Value)
Debug.Print LBound(arr, 1), UBound(arr, 1)
On Error Resume Next
Debug.Print LBound(arr, 2), UBound(arr, 2)
On Error GoTo 0
End Sub
Sub testATRow()
Dim rg As Range: Set rg = Range("A1:E1")
Dim arr As Variant
arr = Application.Transpose(Application.Transpose(rg.Value))
Debug.Print LBound(arr, 1), UBound(arr, 1)
On Error Resume Next
Debug.Print LBound(arr, 2), UBound(arr, 2)
On Error GoTo 0
End Sub
Note that Application.Transpose has a limit of 65535 elements per dimension.
Reduce dimension via Excel function ArrayToText()
If you dispose of version MS 365 you could try the following approach via Excel function ArrayToText() and an eventual split action.
Sub reduceDim()
Dim t#: t = Timer
Dim rng As Range
Set rng = Sheet1.Range("B2:B7") ' << change to your needs
Dim data
data = Split(Evaluate("ArrayToText(" & rng.Address(False, False, External:=True) & ")"), ", ")
Debug.Print "Array(" & LBound(data) & " To " & UBound(data) & ")"
Debug.Print Join(data, "|") ' display resulting 0-based 1-dim array elements
Debug.Print Format(Timer - t, "0.00 secs")
End Sub
Output in VB Editor's immediate window
Array(0 To 5)
1|2|3|4|5|6
0,00 secs

Remove duplicates from a string array using excel VBA

I have the following VBA code in Excel 2010 that reads the numbers in column A into a comma separated string.
The numbers are sorted in column A
However, I was wondering if there was a way to either remove the duplicate numbers while reading them to to the comma separated variable, or a way to remove all the duplicates from the variable after it has been built.
Here is my code that builds the comma separated list
Dim LR As Long
Dim RangeOutput
Dim entry As Variant
Dim FinalResult As String
LR = Range("A" & Rows.Count).End(xlUp).Row
On Error Resume Next 'if only 1 row
For Each entry In ThisWorkbook.Sheets("Sheet1").Range("A2:A" & LR)
If Not IsEmpty(entry.Value) Then
RangeOutput = RangeOutput & entry.Value & ","
End If
Next
FinalResult = Left(RangeOutput, Len(RangeOutput) - 1)
This is how you can do it with a dictionary.
Dim arrData As Variant
Dim dic As Object
Dim idx As Long
arrData = Range("A2").CurrentRegion.Columns(1).Value
Set dic = CreateObject("Scripting.Dictionary")
For idx = 2 To UBound(arrData, 1)
If arrData(idx, 1) <> "" Then
dic(arrData(idx, 1)) = ""
End If
Next idx
FinalResult = Join(dic.keys, ",")
You can use a Collection to achieve same result:
Option Explicit
Sub Test()
Dim ws As Worksheet
'
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("Sheet1")
On Error GoTo 0
If ws Is Nothing Then
MsgBox "Missing required worksheet", vbExclamation, "Cancelled"
Exit Sub
End If
'
Dim lastRowIndex As Long
'
lastRowIndex = ws.Range("A" & Rows.Count).End(xlUp).Row 'Notice the range is fully referenced with the sheet
If lastRowIndex = 1 Then
MsgBox "No data found in column A", vbInformation, "Cancelled"
Exit Sub
End If
'
Dim arrValues As Variant
'
arrValues = ws.Range("A2:A" & lastRowIndex).Value2
If Not IsArray(arrValues) Then arrValues = Array(arrValues) 'Just in case lastRow is 2
'
Dim v As Variant
Dim uniqueValues As New Collection
'
On Error Resume Next 'For duplicated values - collections do not allow duplicated keys
For Each v In arrValues
uniqueValues.Add v, CStr(v)
Next v
On Error GoTo 0
'
Dim arrResult() As Variant
Dim i As Long
Dim result As String
'
ReDim arrResult(0 To uniqueValues.Count - 1)
i = 0
For Each v In uniqueValues
arrResult(i) = CStr(v) 'In case there are errors like #N/A
i = i + 1
Next v
'
result = Join(arrResult, ",")
Debug.Print result
End Sub

Resources