Why when reading my array onto cells in VBA does it repeat every row? - excel

My code simply reads:
Board = Array(1,2,3,4,5,6,7,8,9)
Range("A1:C3") = Board
But when executed repeats 1, 2, 3 across the rows A1 to C1, A2 to C2 and A3 to C3. How do I fix this?

Use a 2D array:
Dim Board(1 To 3, 1 To 3) As Variant 'or As Long
Dim i As Long, j As Long
For i = LBound(Board, 1) To UBound(Board, 1)
For j = LBound(Board, 2) To UBound(Board, 2)
Dim counter As Long
counter = counter + 1
Board(i, j) = counter
Next
Next
Range("A1:C3").Value = Board

Flexible way to slice "flat" array data into range
I assume that Pingu wants to repart a flat array onto the example range.
Furthermore I've been spurred on to this approach by #ScottCraner citing
"You set up a 1D array, that is one row; you will need to make the array the shape you want before trying to post it to the cells.
VBA will not automatically set the array to the shape."
The following function Sliced()` not only tries to this profiting from
a) some evalutions to get the numeric items order,
b) the advanced features of the `Application.Index() function to allow rearranging,
but also to allow a fully flexibilized execution with any column number needed:
Function sliced(arr, Optional ByVal cols As Long = 1)
'Purpose: slice 1-dim array into given number of columns and flexible number of rows
'a) adjust 1-based item numbers into 2-dim array
Dim tmp: tmp = Join(Application.Transpose(Evaluate("row(1:" & cols & ")")), ",")
Dim c: c = Evaluate("row(1:" & UBound(arr) \ cols + 1 & ")*" & cols & "+{" & tmp & "}-" & cols)
'b) return 2dim array sliced into given number of columns and flexible number of rows
sliced = Application.Index(arr, 1, c)
End Function
Example Call
Sub ExampleCall()
'[0]Given input
Dim board: board = Array(10, 20, 30, 40, "50", 60, 70, 80, 90)
Const cols As Long = 3 ' << intended number of columns
'[1]get results array
Dim results: results = sliced(board, cols)
'[2]write to any target
Sheet1.Range("A1").Resize(UBound(results), cols) = results
End Sub

Fill Range with Array
Only run the first procedure which calls the second which calls the third.
Note that a range can be 'filled' by rows and by columns.
Change the values in the first procedure to understand the full potential.
The Code
Option Explicit
Sub TESTfillRangeWithArray()
Dim Board As Variant: Board = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
Dim rng As Range: Set rng = Range("A1:C3")
' Either...
fillRangeWithArray rng, Board ' By Rows
' ...or
'fillRangeWithArray rng, Board, True ' By Columns
End Sub
Sub fillRangeWithArray( _
ByRef rng As Range, _
OneD As Variant, _
Optional ByVal ByColumns As Boolean = False)
' Indexes
Dim FirstIndex As Long: FirstIndex = LBound(OneD)
Dim LastIndex As Long: LastIndex = UBound(OneD)
Dim n As Long: n = FirstIndex - 1
' Limits
Dim rCount As Long: rCount = rng.Rows.Count
Dim cCount As Long: cCount = rng.Columns.Count
' Data Array
Dim Data As Variant
If rCount > 1 Or cCount > 1 Then
ReDim Data(1 To rCount, 1 To cCount)
Else
ReDim Data(1 To 1, 1 To 1)
End If
' Counters
Dim i As Long
Dim j As Long
' Loop
If Not ByColumns Then
For i = 1 To rCount
For j = 1 To cCount
determineIndex n, FirstIndex, LastIndex
Data(i, j) = OneD(n)
Next j
Next i
Else
For j = 1 To cCount
For i = 1 To rCount
determineIndex n, FirstIndex, LastIndex
Data(i, j) = OneD(n)
Next i
Next j
End If
' Result
rng.Value = Data
End Sub
Private Sub determineIndex( _
ByRef CurrentIndex As Long, _
ByVal FirstIndex As Long, _
ByVal LastIndex As Long)
' Indexes
If CurrentIndex < LastIndex Then
CurrentIndex = CurrentIndex + 1
Else
CurrentIndex = FirstIndex
End If
End Sub

Related

Manipulate data in 1-dim array and copy it to a 2-dim array

I have a one-dimensional array with the values below and I want to turn the array in a two-dimensional one, cut the "/*" and save it in the second dimension. The result is supposed to look the second table. I'm trying to utilize a second array for this using the following code but for some reason I get the message that the types are incompatible in the line arr2(i, i) = Mid(arr1(i), 1, arrSffx).
Sub Test2()
Dim arr1 As Variant
Dim arr2 As Variant
Dim i, j, arrSffx, arrLen As Long
arr1 = getUniqueValuesFromRange(Worksheets("table1").UsedRange.Columns("A"))
For i = 0 To UBound(arr1)
arrSffx = InStrRev(arr1(i), "/")
arrLen = Len(arr1(i))
arr2(i, i) = Mid(arr1(i), 1, arrSffx)
arr2(i, i + 1) = Mid(arr1(i), arrSffx, arrLen - arrSffx)
Next i
For i = 0 To UBound(arr2)
Worksheets("table1").Range("D" & i + 2) = arr1(i, i)
Worksheets("table1").Range("D" & i + 2) = arr1(i, i + 1)
Next i
End Sub
You can use this function
Public Function splitArray(arr As Variant, delimiter As String) As Variant
Dim arrReturn As Variant
ReDim arrReturn(UBound(arr), 1)
Dim i As Long, posDelimiter As Long
For i = LBound(arr) To UBound(arr)
posDelimiter = InStr(arr(i), delimiter)
arrReturn(i, 0) = Left(arr(i), posDelimiter - 1)
arrReturn(i, 1) = Mid(arr(i), posDelimiter)
Next
splitArray = arrReturn
End Function
and use it like this
Sub Test2()
Dim arr1 As Variant
Dim arr2 As Variant
arr1 = getUniqueValuesFromRange(Worksheets("table1").UsedRange.Columns("A"))
arr2 = splitArray(arr1, "/")
Dim rgTarget As Range
Set rgTarget = Worksheets("table1").Range("D1")
rgTarget.Resize(UBound(arr2, 1), 2).Value = arr2
End Sub
Its easier if you let the built in functions of vba and other libraries (mscorlib) take the strain.
This solution uses the ArrayList object which can be found in the mscorlib library (add a reference to mscorlib).
It also uses the VBA 'Split' method which can be used to split a string into a number of substrings using a delimiter. In your case you need the delimiter added back to the second string.
Sub Test2()
Dim myUniqueValues As ArrayList
Set myUniqueValues = GetUniqueValuesFromRange(Worksheets("table1").UsedRange.Columns("A"))
Dim myOutput As Variant
ReDim myOutput(1 To myUniqueValues.Count, 1 To 2)
Dim myTmp As Variant
Dim myIndex As Long
myIndex = 1
Dim myItem As Variant
For Each myItem In myUniqueValues
myTmp = VBA.Split(myItem, "/")
myOutput(myIndex, 1) = myTmp(0)
myOutput(myIndex, 2) = "/" & myTmp(1)
myIndex = myIndex + 1
Next
Worksheets("table1").Range("D1:E" & CStr(myUniqueValues.Count)) = myOutput
End Sub
Public Function GetUniqueValuesFromRange(ByVal ipRange As Excel.Range) As ArrayList
Dim myInputArray As Variant
myInputArray = ipRange.Value
Dim myAL As ArrayList
Set myAL = New ArrayList
Dim myItem As Variant
For Each myItem In myInputArray
If Not myAL.Contains(myItem) Then
myAL.Add myItem
End If
Next
Set GetUniqueValuesFromRange = myAL
End Function
Here is heavily commented code on how to transform a 1 dimensional array into a 2 dimensional array by delimiter. The advantage of this method is that is that the result is not hard limited to 2 columns, it can be any number of columns:
'This function tranforms a 1 dimensional array to a 2 dimensional array
'Arguments:
' arg_1D = A 1 dimensional array
' Required
' arg_sDelimiter = The delimiter to split elements on to create a 2 dimensional array
' Optional
' Default value is "/"
' arg_bIncludeDelim = Boolean (True/False) value on whether to include the delimiter in the output results
' Optional
' Default is True
Function Transform_1D_to_2D_Array( _
ByVal arg_a1D As Variant, _
Optional ByVal arg_sDelimiter As String = "/", _
Optional ByVal arg_bIncludeDelim As Boolean = True _
) As Variant
'Verify passed argument is actually a 1 dimensional array
If Not IsArray(arg_a1D) Then
Exit Function 'argument is not an array
Else
Dim lTestExtraDimension As Long
On Error Resume Next
lTestExtraDimension = UBound(arg_a1D, 2) - LBound(arg_a1D, 2) + 1
On Error GoTo 0
If lTestExtraDimension > 0 Then
Exit Function 'argument is an array, but already has more than 1 dimension
End If
End If
'Get maximum number of delimiters in the data
'This allows the resulting 2d array to handle any number of resulting columns
Dim vElement As Variant
Dim lNumDelims As Long, lMax As Long
For Each vElement In arg_a1D
lNumDelims = ((Len(vElement) - Len(Replace(vElement, arg_sDelimiter, vbNullString))) / Len(arg_sDelimiter)) + 1
If lNumDelims > lMax Then lMax = lNumDelims
Next vElement
'Prepare the 2D results array
Dim a2D() As Variant: ReDim a2D(1 To (UBound(arg_a1D) - LBound(arg_a1D) + 1), 1 To lMax)
'Prepare loop variables
Dim aTemp As Variant, vTemp As Variant
Dim lRowIndex As Long, lColIndex As Long
'Loop through 1D array
For Each vElement In arg_a1D
lRowIndex = lRowIndex + 1 'Increase 2D's row index
lColIndex = 0 'Reset 2D's col index
'Split the current 1D array element by the delimiter
aTemp = Split(vElement, arg_sDelimiter)
'Loop through the temporary array that has been created by Split
For Each vTemp In aTemp
lColIndex = lColIndex + 1 'Advance the ColIndex
'If including the delimiter in the results, and if the column index is > 1, add the delimiter to the result
If arg_bIncludeDelim And lColIndex > 1 Then a2D(lRowIndex, lColIndex) = arg_sDelimiter
'Output the result to the appropriate row and column in the 2D array
a2D(lRowIndex, lColIndex) = a2D(lRowIndex, lColIndex) & vTemp
Next vTemp
Next vElement
'Return 2 dimensional results array
Transform_1D_to_2D_Array = a2D
End Function
Here is how you would call it:
Sub tgr()
'Delcare and set worksheet and range variables
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("table1")
Dim rData As Range: Set rData = ws.UsedRange.Columns("A")
'Call function GetUniqueValuesFromRange and populate the results into an array
Dim aUnqVals() As Variant: aUnqVals = GetUniqueValuesFromRange(rData)
'Verify the array has results and that the data range wasn't empty
If UBound(aUnqVals) - LBound(aUnqVals) + 1 = 0 Then
MsgBox "ERROR: No data found in " & rData.Address(External:=True)
Exit Sub
End If
'Call function Transform_1D_to_2D_Array to convert the 1 dimensional array into a 2 dimensional array
Dim aTransformed As Variant: aTransformed = Transform_1D_to_2D_Array(aUnqVals)
'Verify the result is actually an array
If Not IsArray(aTransformed) Then
MsgBox "ERROR: Attempted to transform either a non-array, or array is already multi-dimensional"
Exit Sub
End If
'Output results
ws.Range("D2").Resize(UBound(aTransformed, 1), UBound(aTransformed, 2)).Value = aTransformed
End Sub
And for those interested, this is my take on GetUniqueValuesFromRange:
'This function gets unique values from a range
'Arguments:
' arg_rData = A range object
' Required
' arg_bIgnoreCase = Boolean (True/False) value on whether to ignore case for determing a unique value
' Optional
' Default value is True (case sensitivity will be ignored); AKA "TEST" and "test" will be treated as the same unique value
' arg_bIgnoreBlank = Boolean (True/False) value on whether to ignore blanks in the output results
' Optional
' Default is True (blanks will be ignored)
Function GetUniqueValuesFromRange( _
ByVal arg_rData As Range, _
Optional ByVal arg_bIgnoreCase As Boolean = True, _
Optional ByVal arg_bIgnoreBlank As Boolean = True _
) As Variant()
'Convert the range of values into an array
Dim aData() As Variant
If arg_rData.Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = arg_rData.Value
Else
aData = arg_rData.Value
End If
'Prepare a dictionary object in order to identify unique values
Dim hUnqVals As Object: Set hUnqVals = CreateObject("Scripting.Dictionary")
'If ignoring case sensitivity, set the compare mode to vbTextCompare
If arg_bIgnoreCase Then hUnqVals.CompareMode = vbTextCompare
'Loop through the array of values
Dim vData As Variant
For Each vData In aData
'Test if value is blank
If Len(vData) = 0 Then
'If ignoring blanks, the skip this value, otherwise include it (if not already included)
If arg_bIgnoreBlank = False Then
If hUnqVals.Exists(vData) = False Then hUnqVals.Add vData, vData
End If
Else
'Value not blank, include it (if not already included)
If hUnqVals.Exists(vData) = False Then hUnqVals.Add vData, vData
End If
Next vData
'Return unique values
GetUniqueValuesFromRange = hUnqVals.Keys
End Function
Image showing source data and results (with an example of one of the data points requiring a third column based on the delimiter):
Arrays: 1D to 2D with Split
Sub OneDToTwoD()
' Reference the Source range.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Table1")
Dim srg As Range: Set srg = ws.UsedRange.Columns("A")
' Using the 'getUniqueValuesFromRange' function,
' return the unique values of the Source range in a 1D array.
Dim Arr As Variant: Arr = getUniqueValuesFromRange(srg)
' Split the strings in the 1D array and return the substrings
' in a 2D one-based two-column array.
Dim rCount As Long: rCount = UBound(Arr) - LBound(Arr) + 1
Dim Data() As Variant: ReDim Data(1 To rCount, 1 To 2)
Dim i As Long, r As Long, strPos As Long, strLen As Long
For i = LBound(Arr) To UBound(Arr)
strPos = InStrRev(Arr(i), "/")
strLen = Len(Arr(i))
r = r + 1
Data(r, 1) = Mid(Arr(i), 1, strPos - 1) ' exclude delimiter
Data(r, 2) = Mid(Arr(i), strPos, strLen - strPos + 1) ' include delim. ?
'Data(r, 2) = Mid(Arr(i), strPos + 1, strLen - strPos) ' exclude delim.
Next i
' Reference the Destination range.
Dim drg As Range: Set drg = ws.Range("D2").Resize(rCount, 2)
' Write the values from the 2D array to the Destination range.
drg.Value = Data
End Sub
If your function doesn't exclude the first row you might want to reference the source range in the following way:
Dim srg As Range
With ws.UsedRange.Columns("A")
Set srg = .Resize(.Rows.Count - 1).Offset(1)
End With

Get top five students with highest marks

I have searched and found a code that extracts the top five names with the highest marks. The code is OK and I can get the names and marks
Sub Test_GetTopFive()
GetTopFive Range("A2:B" & Cells(Rows.Count, 1).End(xlUp).Row)
End Sub
Sub GetTopFive(r As Range)
Dim v, t, i As Long
t = Application.WorksheetFunction.Aggregate(14, 6, r.Columns(2), 5)
v = r
For i = 1 To UBound(v, 1)
If Not IsError(v(i, 1)) Then
If v(i, 2) >= t Then
Debug.Print v(i, 1), v(i, 2)
End If
End If
Next i
End Sub
But the results in the immediate window are not sorted. I need to get the names with the highest marks first.
Have a go with the code below. The tricky part is you can't just sort arrays, So I instead have it loop the number of results you want, then for each of those it loops through the array to find the max value. Once found it prints it, then sets it's value to 0 to remove it from being looked at in the next result.
Sub Test_GetTopFive()
GetTopFive Range("A2:B" & Cells(Rows.Count, 1).End(xlUp).Row)
End Sub
Sub GetTopFive(r As Range)
Dim v, t, m, i As Long, j As Long, rw As Long
t = Application.WorksheetFunction.Aggregate(14, 6, r.Columns(2), 5)
m = t - 1
v = r
For i = 1 To 5
For j = 1 To UBound(v, 1)
If Not IsError(v(j, 2)) Then
If v(j, 2) >= t Then
If v(j, 2) > m Then
m = v(j, 2)
rw = j
End If
End If
End If
Next j
If rw > 0 Then
Debug.Print v(rw, 1), v(rw, 2)
v(rw, 2) = 0
m = t - 1
rw = 0
End If
Next i
End Sub
I don't understand why you are using VBA for that: in order to get the five larges values (e.g. from range A2:A10), I just type those five formulas (e.g. in range "C1:C5"):
=LARGE(A$2:A$10,1) 'in cell C1, there you get the largest value.
=LARGE(A$2:A$10,2) 'in cell C2, there you get the second largest value.
=LARGE(A$2:A$10,3) 'in cell C3, there you get the third largest value.
=LARGE(A$2:A$10,4) 'in cell C4, there you get the fourth largest value.
=LARGE(A$2:A$10,5) 'in cell C5, there you get the fifth largest value.
VBA Top Values
Some Issues
In this case, the WorksheetFunction.Aggregate function will raise an error e.g. if there are less than 5 numeric values. What to do in such a case?
How to resolve the ties? Pick the first appearing in the range?
Application.Max will return an error if there are error values.
Application.Max will not consider blanks as zeros.
Application.Match will return an error if there was no match.
What if there are negative numbers (ridiculous in this case)?
Option Explicit
Sub Test_DebugPrintTop()
Dim rg As Range: Set rg = Range("A2:B" & Cells(Rows.Count, 1).End(xlUp).Row)
DebugPrintTop rg, 5, False
End Sub
Sub DebugPrintTop( _
ByVal rg As Range, _
ByVal TopCount As Long, _
Optional ByVal IncludeBlanks As Boolean = False)
If rg Is Nothing Then
Debug.Print "No range."
Exit Sub
End If
If TopCount < 1 Then
Debug.Print "'TopCount' has to be a positive integer."
Exit Sub
End If
Dim sData As Variant: sData = rg.Resize(, 2).Value ' only 2 columns
Dim sData2 As Variant: sData2 = rg.Columns(2).Value ' 2nd column
Dim srCount As Long: srCount = UBound(sData, 1)
Dim r As Long
Dim srValue As Variant: srValue = Application.Max(sData2)
If IsError(srValue) Then
For r = 1 To srCount
' Check for error values and replace them with 'Empty' values.
If IsError(sData2(r, 1)) Then
sData(r, 2) = Empty
sData2(r, 1) = Empty
End If
Next r
End If
If IncludeBlanks Then
For r = 1 To srCount
' Check for blanks and replace them with zeros.
If Len(sData2(r, 1)) = 0 Then
sData(r, 2) = 0
sData2(r, 1) = 0
End If
Next r
End If
Dim srIndexes() As Long
Dim srIndex As Variant
Dim drCount As Long
For r = 1 To TopCount
srValue = Application.Max(sData2)
srIndex = Application.Match(srValue, sData2, 0)
If IsNumeric(srIndex) Then
drCount = drCount + 1
ReDim Preserve srIndexes(1 To drCount)
srIndexes(drCount) = srIndex
sData2(srIndex, 1) = Empty ' not 0
Else
Exit For
End If
Next r
If drCount = 0 Then
Debug.Print "No numbers."
Exit Sub
End If
For r = 1 To drCount
Debug.Print sData(srIndexes(r), 1), sData(srIndexes(r), 2)
Next r
' An idea to make e.g. the 'GetTop' function from it.
' Dim dData As Variant: ReDim dData(1 To drCount, 1 To 2)
' For r = 1 To drCount
' dData(r, 1) = sData(srIndexes(r), 1)
' dData(r, 2) = sData(srIndexes(r), 2)
' Next r
' GetTop = dData
End Sub

Do a loop with multiple constant condition VBA

I am trying to do a loop but I'm a little stuck.
Sub Macro()
Range("A392: A401").Value = Range("N2")
Range("A402: A411").Value = Range("N3")
Range("A412: A421").Value = Range("N4")
Range("A422: A431").Value = Range("N5")
....
I need to repeat this logic ( On column A to set a value for each 10 rows) this value will be from Column N from 1 to 1 until it finds an empty row ...
I'm not being able to do the loop with these multiples conditions, would you please help me ?
Thanks a lot!
Range.Offset is a great method to manipulate ranges. Using it, we can automate the ranges to move down the sheet with each loop.
Sub Macro()
Dim i As Long
While Range("N2").Offset(i) <> ""
'Offset will shift N2 down by one each loop
'Offset will shift the 10 cell range down by 10 on each loop
Range("A392: A401").Offset(i * 10).Value = Range("N2").Offset(i)
i = i + 1
Wend
End Sub
I'm not quite sure what's your actual intention:
Repeat all values in column N2:N5 10 times and append the whole data block to the first free cell in column A.
Repeat all non-empty values in column N 10 times and write them to a fixed target starting with cell A392.
In both cases you can prefill an array and write it to the defined target in column A. Looping through an array has some speed advantages whereas looping through a range by means of VBA can be time consuming.
The direct (untested) copying of whole blocks as shown by #Toddleson can improve this behaviour, depending on total range sizes.
Case 1
Sub Example1()
Const RowsCount As Long = 10
With Sheet1 ' << change to your project's sheet (Code)Name
'get values to repeat & count them
Dim vals: vals = .Range("N2:N5")
Dim cnt As Long: cnt = UBound(vals)
'provide for 1-based 2-dim results array
Dim results
ReDim results(1 To RowsCount * cnt, 1 To 1)
'fill array with repeated values
Dim i As Long, j As Long
For i = 1 To cnt
For j = 1 To RowsCount
results((i - 1) * RowsCount + j, 1) = vals(i, 1)
Next j
Next i
'append above data block
Dim nxtRow As Long
nxtRow = .Range("A" & .Rows.Count).End(xlUp).Row + 1
.Range("A" & nxtRow).Resize(UBound(results), 1) = results
End With
End Sub
Case 2
Sub Example2()
Const RowsCount As Long = 10
With Sheet1 ' << change to your project's sheet (Code)Name
'get values to repeat & count them
Dim lastRow As Long
lastRow = .Range("N" & .Rows.Count).End(xlUp).Row
Dim vals: vals = .Range("N2:N" & lastRow)
Dim ValsCount As Long: ValsCount = UBound(vals)
'provide for 1-based 2-dim results array
Dim results
ReDim results(1 To RowsCount * ValsCount, 1 To 1)
'fill array with repeated values
Dim i As Long, j As Long, ii As Long
For i = 1 To ValsCount
If Len(vals(i, 1)) > 0 Then ' check if non-empty value in column N
ii = ii + 1
For j = 1 To RowsCount
results((ii - 1) * RowsCount + j, 1) = vals(i, 1)
Next j
End If
Next i
'write data block to fixed target starting with A392
.Range("A392").Resize(UBound(results), 1) = results
End With
End Sub

How to pivot duplicate rows to columns?

having a hard time figuring out how to pivot a multi-column data set with duplicate rows into unique columns.
I have done research and found some VBA scripts to do this, but it is resulting in data missing when I do counts to confirm it pivoted correctly and ends up adding in duplicate columns (name/ rating year) over and over.
Anyone have any ideas? I'd do a pivot table, but I can't display the actual rating values in a pivot, only a sum/count/avg. etc...
You can do this easily in powerquery.
Highlight all your data, then insert>add table
data tab>get data from table
highlight right two columns>pivot columns
rating level as values
advanced options>don't aggregate
find and replace null with nothing
save and close
Pivot Data
The Code
Option Explicit
Sub pivotData()
' Define Source Range.
Dim rng As Range
Set rng = Range("A1").CurrentRegion
' Get unique values.
Dim prs As Variant
prs = getUniqueColumn1D(rng.Columns(1).Resize(rng.Rows.Count - 1).Offset(1))
Dim yrs As Variant
yrs = getUniqueColumn1D(rng.Columns(2).Resize(rng.Rows.Count - 1).Offset(1))
sort1D yrs
' Source Range to Source Array.
Dim Source As Variant
Source = rng.Value
' Define Target Array.
Dim Target As Variant
ReDim Target(1 To UBound(prs) - LBound(prs) + 2, _
1 To UBound(yrs) - LBound(yrs) + 2)
' Write from arrays to Target Array.
Target(1, 1) = Source(1, 1)
Dim n As Long
Dim i As Long
i = 1
For n = LBound(prs) To UBound(prs)
i = i + 1
Target(i, 1) = prs(n)
Next n
Dim j As Long
j = 1
For n = LBound(yrs) To UBound(yrs)
j = j + 1
Target(1, j) = yrs(n)
Next n
For n = 2 To UBound(Source, 1)
i = Application.Match(Source(n, 1), prs, 0) + 1
j = Application.Match(Source(n, 2), yrs, 0) + 1
Target(i, j) = Source(n, 3)
Next n
' Define Target Range.
Set rng = Range("E1").Resize(UBound(Target, 1), UBound(Target, 2))
' Write from Target Array to Target Range.
rng.Value = Target
' Inform user.
MsgBox "Data transferred.", vbInformation, "Success"
End Sub
' Returns the unique values from a column range.
Function getUniqueColumn1D(ColumnRange As Range, _
Optional ByVal Sorted As Boolean = False) _
As Variant
Dim Data As Variant
Data = ColumnRange.Columns(1).Value
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
Dim Key As Variant
Dim i As Long
For i = 1 To UBound(Data, 1)
Key = Data(i, 1)
If Not IsError(Key) And Not IsEmpty(Key) Then
.Item(Key) = Empty
End If
Next i
If .Count > 0 Then
getUniqueColumn1D = .Keys
End If
End With
End Function
' Sorts a 1D array only if it contains values of the same data type.
Sub sort1D(ByRef OneD As Variant, _
Optional ByVal Descending As Boolean = False)
With CreateObject("System.Collections.ArrayList")
Dim i As Long
For i = LBound(OneD) To UBound(OneD)
.Add OneD(i)
Next i
.Sort
If Descending Then
.Reverse
End If
OneD = .ToArray
End With
End Sub

Remove duplicates from array using VBA

Assume I have a block of data in Excel 2010, 100 rows by 3 columns.
Column C contains some duplicates, say it starts off as
1, 1, 1, 2, 3, 4, 5, ..... , 97, 98
Using VBA, I would like to remove the duplicate rows so I am left with 98 rows and 3 columns.
1, 2, 3, ..... , 97, 98
I know there is a button in Excel 2010 to do that but it inteferes with the rest of my code subsequently and gives incorrect results.
Furthermore, I would like to do it in arrays, then paste the results on the worksheet, rather than methods such as Application.Worksheetfunction.countif(.....
So something like:
Dim myarray() as Variant
myarray=cells(1,1).Currentregion.value
Dim a as Long
For a=1 to Ubound(myarray,1)
'something here to
Next a
I answered a similar question. Here is the code I used:
Dim dict As Object
Dim rowCount As Long
Dim strVal As String
Set dict = CreateObject("Scripting.Dictionary")
rowCount = Sheet1.Range("A1").CurrentRegion.Rows.Count
'you can change the loop condition to iterate through the array rows instead
Do While rowCount > 1
strVal = Sheet1.Cells(rowCount, 1).Value2
If dict.exists(strVal) Then
Sheet1.Rows(rowCount).EntireRow.Delete
Else
'if doing this with an array, then add code in the Else block
' to assign values from this row to the array of unique values
dict.Add strVal, 0
End If
rowCount = rowCount - 1
Loop
Set dict = Nothing
If you want to use an array, then loop through the elements with the same conditional (if/else) statements. If the item doesn't exist in the dictionary, then you can add it to the dictionary and add the row values to another array.
Honestly, I think the most efficient way is to adapt code you'd get from the macro recorder. You can perform the above function in one line:
Sheet1.UsedRange.RemoveDuplicates Columns:=3, Header:=xlYes
Function eliminateDuplicate(poArr As Variant) As Variant
Dim poArrNoDup()
dupArrIndex = -1
For i = LBound(poArr) To UBound(poArr)
dupBool = False
For j = LBound(poArr) To i
If poArr(i) = poArr(j) And Not i = j Then
dupBool = True
End If
Next j
If dupBool = False Then
dupArrIndex = dupArrIndex + 1
ReDim Preserve poArrNoDup(dupArrIndex)
poArrNoDup(dupArrIndex) = poArr(i)
End If
Next i
eliminateDuplicate = poArrNoDup
End Function
Simple function to remove duplicates from a 1D array
Private Function DeDupeArray(vArray As Variant) As Variant
Dim oDict As Object, i As Long
Set oDict = CreateObject("Scripting.Dictionary")
For i = LBound(vArray) To UBound(vArray)
oDict(vArray(i)) = True
Next
DeDupeArray = oDict.keys()
End Function
Edit:
With stdVBA (a library largely maintained by myself) you can use:
uniqueValues = stdEnumerator.CreateFromArray(myArray).Unique().AsArray()
An improvement on #RBILLC and #radoslav006 answers, this version searches the array with the duplicates removed for existing values so it searchs less values to find a duplicate.
Public Function RemoveDuplicatesFromArray(sourceArray As Variant)
Dim duplicateFound As Boolean
Dim arrayIndex As Integer, i As Integer, j As Integer
Dim deduplicatedArray() As Variant
arrayIndex = -1
deduplicatedArray = Array(1)
For i = LBound(sourceArray) To UBound(sourceArray)
duplicateFound = False
For j = LBound(deduplicatedArray) To UBound(deduplicatedArray)
If sourceArray(i) = deduplicatedArray(j) Then
duplicateFound = True
Exit For
End If
Next j
If duplicateFound = False Then
arrayIndex = arrayIndex + 1
ReDim Preserve deduplicatedArray(arrayIndex)
deduplicatedArray(arrayIndex) = sourceArray(i)
End If
Next i
RemoveDuplicatesFromArray = deduplicatedArray
End Function
Here's another approach for working with an array:
Sub tester()
Dim arr, arrout
arr = Range("A1").CurrentRegion.Value 'collect the input array
arrout = UniqueRows(arr) 'get only unique rows
Range("H1").Resize(UBound(arrout, 1), UBound(arrout, 2)).Value = arrout
End Sub
Function UniqueRows(arrIn As Variant) As Variant
Dim keys, rw As Long, col As Long, k, sep, arrout
Dim dict As Object, lbr As Long, lbc As Long, ubr As Long, ubc As Long, rwOut As Long
Set dict = CreateObject("scripting.dictionary")
'input array bounds
lbr = LBound(arrIn, 1)
ubr = UBound(arrIn, 1)
lbc = LBound(arrIn, 2)
ubc = UBound(arrIn, 2)
ReDim keys(lbr To ubr)
'First pass:collect all the row "keys" in an array
' and unique keys in a dictionary
For rw = lbr To ubr
k = "": sep = ""
For col = lbc To ubc
k = k & sep & arrIn(rw, col)
sep = Chr(0)
Next col
keys(rw) = k 'collect key for this row
dict(k) = True 'just collecting unique keys
Next rw
'Resize output array to # of unique rows
ReDim arrout(lbr To dict.Count + (lbr - 1), lbc To ubc)
rwOut = lbr
'Second pass: copy each unique row to the output array
For rw = lbr To ubr
If dict(keys(rw)) Then 'not yet output?
For col = lbc To ubc 'copying this row over to output...
arrout(rwOut, col) = arrIn(rw, col)
Next col
rwOut = rwOut + 1 'increment output "row"
dict(keys(rw)) = False 'flag this key as copied
End If
Next rw
UniqueRows = arrout
End Function
Answer from #RBILLC could be easily improved by adding an Exit For inside internal loop:
Function eliminateDuplicate(poArr As Variant) As Variant
Dim poArrNoDup()
dupArrIndex = -1
For i = LBound(poArr) To UBound(poArr)
dupBool = False
For j = LBound(poArr) To i
If poArr(i) = poArr(j) And Not i = j Then
dupBool = True
Exit For
End If
Next j
If dupBool = False Then
dupArrIndex = dupArrIndex + 1
ReDim Preserve poArrNoDup(dupArrIndex)
poArrNoDup(dupArrIndex) = poArr(i)
End If
Next i
eliminateDuplicate = poArrNoDup
End Function
I think this is really a case for using excel's native functions, at least for the initial array acquisition, and I don't think there's any simpler way to do it. This sub will output the unique values starting in column 5. I assumed that the target range was empty, so if it's not, change r and c.
Sub testUniques()
Dim arr, r As Long, c As Long, h As Long, w As Long
Dim this As Worksheet: Set this = ActiveSheet
arr = Application.Unique(this.Cells(1, 1).CurrentRegion)
r = 1
c = 5
h = UBound(arr, 1) - 1
w = UBound(arr, 2) - 1
this.Range(this.Cells(r, c), this.Cells(r + h, c + w)) = arr
End Sub
I know this is old, but here's something I used to copy duplicate values to another range so that I could see them quickly to establish data integrity for a database I was standing up from various spreadsheets. To make the procedure delete the duplicates it would be as simple as replacing the dupRng lines with Cell.Delete Shift:=xlToLeft or something to that effect.
I haven't tested that personally, but it should work.
Sub PartCompare()
Dim partRng As Range, partArr() As Variant, i As Integer
Dim Cell As Range, lrow As Integer
lrow = ThisWorkbook.Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row
i = 0
Set partRng = ThisWorkbook.Worksheets("Sheet1").Range(Cells(1, 1), Cells(lrow, 1))
For Each Cell In partRng.Cells
ReDim Preserve partArr(i)
partArr(i) = Cell.Value
i = i + 1
Next
Dim dupRng As Range, j As Integer, x As Integer, c As Integer
Set dupRng = ThisWorkbook.Worksheets("Sheet1").Range("D1")
x = 0
c = 1
For Each Cell In partRng.Cells
For j = c To UBound(partArr)
If partArr(j) = Cell.Value Then
dupRng.Offset(x, 0).Value = Cell.Value
dupRng.Offset(x, 1).Value = Cell.Address()
x = x + 1
Exit For
End If
Next j
c = c + 1
Next Cell
End Sub
Remove duplicates (plus related row items) from array
As OP wanted a VBA solution close to RemoveDuplicates, I demonstrate an array approach using a â–ºdictionary to get not the unique items per se (dict.keys), but the related row indices of first occurrencies (dict.items).
These are used to retain the whole row data via procedure LeaveUniques profiting from the advanced possibilities of the â–ºApplication.Index() function - c.f. Some peculiarities of the the Application.Index function
Example Call
Sub ExampleCall()
'[0]define range and assign data to 1-based 2-dim datafield
With Sheet1 ' << reference to your project's sheet Code(Name)
Dim lastRow: lastRow = .Cells(.Rows.Count, "C").End(xlUp).Row
Dim rng: Set rng = .Range("C2:E" & lastRow)
End With
Dim data: data = rng ' assign data to 2-dim datafield
'[1]get uniques (column 1) and remove duplicate rows
LeaveUniques data ' << call procedure LeaveUniques (c.f. RemoveDuplicates)
'[2]overwrite original range
rng.Clear
rng.Resize(UBound(data), UBound(data, 2)) = data
End Sub
Procedure LeaveUniques
Sub LeaveUniques(ByRef data As Variant, Optional ByVal colNum As Long = 1)
'Purpose: procedure removes duplicates of given column number in entire array
data = Application.Index(data, uniqueRowIndices(data, colNum), nColIndices(UBound(data, 2)))
End Sub
Help functions to LeaveUniques
Function uniqueRowIndices(data, Optional ByVal colNum As Long = 1)
'Purpose: return data index numbers referring to uniques
'a) set late bound dictionary to memory
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
'b) slice e.g. first data column (colNum = 1)
Dim colData
colData = Application.Index(data, 0, colNum)
'c) fill dictionary with uniques referring to first occurencies
Dim i As Long
For i = 1 To UBound(colData)
If Not dict.exists(dict(colData(i, 1))) Then dict(colData(i, 1)) = i
Next
'd) return 2-dim array of valid unique 1-based index numbers
uniqueRowIndices = Application.Transpose(dict.items)
End Function
Function nColIndices(ByVal n As Long)
'Purpose: return "flat" array of n column indices, e.g. for n = 3 ~> Array(1, 2, 3)
nColIndices = Application.Transpose(Evaluate("row(1:" & n & ")"))
End Function

Resources