How to skip "nan" or blanks in my vba code - excel

I have an excel sheet with bunch of "nan" and blank cells and i don't want my for loop to evaluate these as shown below in the image
![image][1]
This is my code below and it's taking all the blank cell values as 0 and messing with my code.
For j = LBound(ARINC_CHA_DATA_array, 2) To UBound(ARINC_CHA_DATA_array, 2)
For i = LBound(ARINC_CHA_DATA_array, 1) To UBound(ARINC_CHA_DATA_array, 1)
If ARINC_CHA_DATA_array(i, j) <> "" Or ARINC_CHA_DATA_array(i, j) = "nan" Then GoTo Ravi
...........code
...........code
End If
Ravi:
Next i
Next j
```[![enter image description here][2]][2]
[1]: https://i.stack.imgur.com/ip2B9.png
[2]: https://i.stack.imgur.com/bTDou.png

Select Case vs If...Then...Else When Looping Through an Array
Use shorter variable names to make the code more readable.
GoTo is kind of reserved for error-handling and its use can most often be avoided.
The opposite of A = B OR C = D is A <> B AND C <> D.
That is why Test2 is preferred compared to Test3 (no need for the Else statement).
All three codes do the same: they read the values from a range into an array, then loop through the elements of the array doing ... code (at the moment nothing) and write the 'modified' values from the array back to the range.
Caution: If there are formulas in the range, after running this code, they will be replaced with values.
Option Explicit
Sub Test1()
Dim rg As Range: Set rg = ActiveSheet.Range("A1:J10")
Dim Acd As Variant: Acd = rg.Value
Dim Item As Variant
Dim j As Long, i As Long
For j = LBound(Acd, 2) To UBound(Acd, 2)
For i = LBound(Acd, 1) To UBound(Acd, 1)
Item = Acd(i, j)
Select Case CStr(Item)
Case "", "nan"
' do nothing
Case Else
' ... code
End Select
Next i
Next j
rg.Value = Acd
End Sub
Sub Test2()
Dim rg As Range: Set rg = ActiveSheet.Range("A1:J10")
Dim Acd As Variant: Acd = rg.Value
Dim Item As Variant
Dim j As Long, i As Long
For j = LBound(Acd, 2) To UBound(Acd, 2)
For i = LBound(Acd, 1) To UBound(Acd, 1)
Item = Acd(i, j)
If CStr(Item) <> "" And CStr(Item) <> "nan" Then
' ... code
'Else ' you don't need this
' do nothing
End If
Next i
Next j
rg.Value = Acd
End Sub
Sub Test3()
Dim rg As Range: Set rg = ActiveSheet.Range("A1:J10")
Dim Acd As Variant: Acd = rg.Value
Dim Item As Variant
Dim j As Long, i As Long
For j = LBound(Acd, 2) To UBound(Acd, 2)
For i = LBound(Acd, 1) To UBound(Acd, 1)
Item = Acd(i, j)
If CStr(Item) = "" Or CStr(Item) = "nan" Then
' do nothing
Else
' ... code
End If
Next i
Next j
rg.Value = Acd
End Sub

Related

how to suppress efficiently all empty rows in selected columns in excel vba?

I have found some Q/A to delete rows with empty cells in a chosen column like here. My need is a bit different, the columns are selected by the user, but this is not important.
EDIT : what is important in my use case is to delete the rows where all the cells are empty for these columns i.e. the selected columns.
The following code is working, but can only process 1,000 lines per minute on my i5. In my use case, the datasheet contains several 100k lines which means hours to process. This is not acceptable. Is there a trick to perfom it quickly please?
Sub DeleteRowsOfEmptyColumn() 'sh As Worksheet, col As String)
Application.ScreenUpdating = False
Dim sh As Excel.Worksheet: Set sh = ActiveWorkbook.ActiveSheet
Dim col As Range: Set col = Selection.EntireColumn
Dim cell
Dim area As Range: Set area = Intersect(sh.UsedRange, col)
For i = area.Rows.Count To 1 Step -1 'For Each row In area.Rows
fKeep = False
For Each cell In area.Rows(i).Cells
If Not IsEmpty(cell) Then
fKeep = True
Exit For
End If
Next cell
If Not fKeep Then
sh.Rows(i).Delete 'rowsToDelete.Add i
End If
Next i
Application.ScreenUpdating = True
End Sub
Example:
Before:
After:
Delete Empty Row Ranges
This is a basic example. Your feedback regarding the efficiency is appreciated.
Option Explicit
Sub DeleteRowsOfEmptyColumn()
Application.ScreenUpdating = False
Dim ws As Worksheet: Set ws = ActiveSheet ' improve
Dim crg As Range: Set crg = Selection.EntireColumn ' Columns Range
Dim srg As Range: Set srg = Intersect(ws.UsedRange, crg) ' Source Range
Dim drg As Range ' Delete Range
Dim arg As Range ' Area Range
Dim rrg As Range ' Row Range
For Each arg In srg.Areas
For Each rrg In arg.Rows
If Application.CountA(rrg) = 0 Then
If drg Is Nothing Then
Set drg = rrg
Else
Set drg = Union(drg, rrg)
End If
End If
Next rrg
Next arg
If Not drg Is Nothing Then drg.Delete
Application.ScreenUpdating = True
MsgBox "Rows deleted.", vbInformation
End Sub
Please, try the next way. It will process selected columns or columns having at least a selected cell. It will delete entire rows of the sheet, for the cases of all selected columns empty rows. The code only selects the rows in discussion. If they are the appropriate ones, on the last code line, Select should be replaced with Delete. It should be very fast, even for larger ranges, iterating only between blank cells range:
Sub DeleteRowsOfEmptyColumnsCells()
Dim sh As Excel.Worksheet: Set sh = ActiveSheet
Dim col As Range: Set col = Selection.EntireColumn
Dim area As Range: Set area = Intersect(sh.UsedRange, col)
Dim firstCol As Long: firstCol = area.Column: Stop
Dim areaV As Range, arr, rngDel As Range, i As Long
On Error Resume Next 'only for the case of no any empty rows existence
Set areaV = area.SpecialCells(xlCellTypeBlanks) 'a range of only empty cells
On Error GoTo 0
arr = getRows(areaV) 'extract all rows and number of columns
For i = 0 To UBound(arr(0)) 'iterate between all existing rows
If Intersect(sh.rows(arr(0)(i)), areaV).cells.count = arr(1) Then
If rngDel Is Nothing Then
Set rngDel = sh.cells(arr(0)(i), firstCol)
Else
Set rngDel = Union(rngDel, sh.cells(arr(0)(i), firstCol))
End If
End If
Next i
If Not rngDel Is Nothing Then rngDel.EntireRow.Select 'if it looks OK, Select should be replaced with Delete
End Sub
Function getRows(rng As Range) As Variant
Dim A As Range, i As Long, countC As Long
Dim arrCol, arrR, k As Long, R As Long, mtchC, mtchR
ReDim arrCol(rng.cells.count): ReDim arrR(rng.cells.count)
For Each A In rng.Areas
For i = 1 To A.Columns.count
For j = 1 To A.rows.count
mtchC = Application.match(A.cells(j, i).Column, arrCol, 0)
mtchR = Application.match(A.cells(j, i).row, arrR, 0)
If IsError(mtchC) Then
arrCol(k) = A.cells(j, i).Column: k = k + 1
End If
If IsError(mtchR) Then
arrR(R) = A.cells(j, i).row: R = R + 1
End If
Next j
Next i
Next A
ReDim Preserve arrR(R - 1)
getRows = Array(arrR, k)
End Function
I am working on similar kind of project. I have chosen to read the data into an array, and then work with the data in the array which improves run time significantly. Here is a copy of the function that I have used to delete / transform the data set:
Option Explicit
Option Base 1
Public Function RemoveRowFromArray(Arr As Variant, Element As String, Col As Long) As Variant
Dim i, j, c, count As Long
Dim TempArr() As Variant
For i = LBound(Arr, 1) To UBound(Arr, 1) ' looping through the columns to get desired value
If Arr(i, Col) = Element Then
count = count + 1 ' Counting the number of Elements in array / matrix
For j = i To (UBound(Arr, 1) - 1) ' Looping from the row where Element is found
For c = LBound(Arr, 2) To UBound(Arr, 2) ' Moving all elements in row 1 row up
Arr(j, c) = Arr(j + 1, c)
Next c
Next j
End If
Next i
' Populating TempArr to delete the last rows
ReDim TempArr((UBound(Arr, 1) - count), UBound(Arr, 2))
For i = LBound(TempArr, 1) To UBound(TempArr, 1)
For j = LBound(TempArr, 2) To UBound(TempArr, 2)
TempArr(i, j) = Arr(i, j)
Next j
Next i
RemoveRowFromArray = TempArr
End Function
I tested this and seems to work perfectly. A few important matters to keep in mind
Option Base 1 - This is important, when you declare an arr in VBA it starts at Index 0, when you read the arr from a data set in Excel [arr = sheet1.Range("A:D")] then the arr starting index is 1, Option Base 1 will ensure that all arr start at Index 1.
The function variables are :
Arr - the array / matrix
Element - the string that you wish to search for (in your case it would be blank)
Col - is the column number in which Element is.

Running Total Excel or VBA functionReset Based on Cell value

Hi I have a column of 0's and 1's I want to create a running total of the non 0 values un-till it reaches a cell value of 0. Once it hits zero it should, return an empty cell, reset to 0, and begin again from 1 at the next cell value of 1.
Any help would be appreciated, including what I might want to look at to help.
Editing with current solution:
Ive found this solution that works, how would I go about making this a function instead of using this Sub()?
Sub test()
Dim value As Integer
value = 0
For i = 1 To Range("Table2").Rows.Count
If ThisWorkbook.Worksheets("Sheet1").Range("Table2[Current Col]").Cells(i) = 0 Then
value = 0
Range("Table2[New Column]")(i) = ""
ElseIf ThisWorkbook.Worksheets("Sheet1").Range("Table2[Current Col]").Cells(i) = 1 Then
value = value + 1
Range("Table2[New Column]")(i) = value
End If
Next i
End Sub
Incrementing Groups
Use variables to avoid long unreadable lines.
Option Explicit
Sub test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim srg As Range: Set srg = ws.Range("Table2[Current Col]")
Dim drg As Range: Set drg = ws.Range("Table2[New Col]")
Dim sValue As Variant
Dim dValue As Variant
Dim iValue As Long
Dim i As Long
For i = 1 To srg.Cells.Count
' Read from source cell into a variable ('sValue').
sValue = srg.Cells(i).Value
' Test and write result to a variable ('dValue').
If IsNumeric(sValue) Then
If sValue = 1 Then
iValue = iValue + 1
dValue = iValue
End If
Else
iValue = 0
dValue = Empty
End If
' Write from the variable ('dValue') to the destination cell.
drg.Cells(i).Value = dValue
Next i
End Sub
As a UDF:
Function CountUp(rng As Range)
Dim arr, arrOut(), v As Long, i As Long
arr = rng.Columns(1).value
ReDim arrOut(1 To UBound(arr, 1), 1 To UBound(arr, 2))
v = 0
For i = 1 To UBound(arr, 1)
v = IIf(arr(i, 1) = 1, v + 1, 0)
arrOut(i, 1) = v
Next i
CountUp = arrOut
End Function
If your Excel version has the "autospill" feature then you can enter it as a normal function: if not then you need to select the whole output range and enter the formula using Ctrl+Shift+Enter

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

Nesting For loops in VBA

Sub adress()
Dim s As Long
Dim h As Long
Dim n As Long
Dim i As Long
s = 1
n = 1
h = 1
For n = 1 To 1800
For i = 1 To 2000
If ActiveSheet.Cells(h + 1, 13) = ActiveSheet.Cells(s + 1, 32) Then
ActiveSheet.Cells(h + 1, 48) = ActiveSheet.Cells(s + 1, 36)
ActiveSheet.Cells(h + 1, 51) = ActiveSheet.Cells(s + 1, 37)
End If
s = s + 1
Next i
h = h + 1
i = 1
Next n
End Sub
This code is written to grab a value in a column of an excel spread sheet, then go to the next column and search the whole column for a matching value. Once that is found it will print the value that is in a cell in the same row of the value in the second column it found, into a cell in the same row as the original value it was trying to match.
While the inner loop works and my code will do the correct process when ran, it only does it for one value in the first column. I have tried using ranges in the For Loops, I have tried do while loops and do until loops. If i manually change the value of "h" and run the code it will progress down the column and print the correct information but i cannot get "h" to update on its own.
Nested For Next Loops
Although Exit For and turning off the two application settings are used, the first procedure still takes 45 seconds on my machine (without the 'improvements' it might take half an hour).
In the second code the inner loop is replaced with Application.Match and the operations are performed using arrays. It takes less than a second.
The Code
Option Explicit
Sub loopSlow()
Dim i As Long
Dim k As Long
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With ActiveSheet
For i = 2 To 1801
For k = 2 To 2001
If .Cells(i, 13).Value = .Cells(k, 32).Value Then
.Cells(i, 48).Value = .Cells(k, 36).Value
.Cells(i, 51).Value = .Cells(k, 37).Value
Exit For
End If
Next k
Next i
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Sub loopFast()
' Source
Const sName As String = "Sheet2"
Const sColsList As String = "AF,AJ,AK"
Const sFirstRow As Long = 2
' Destination (Lookup)
Const dName As String = "Sheet2"
Const dColsList As String = "M,AV,AY"
Const dFirstRow As Long = 2
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Declare variables.
Dim ws As Worksheet
Dim rg As Range
Dim Cols() As String
Dim cUpper As Long
Dim cOffset As Long
Dim n As Long
' Write values from Source Columns to arrays of Data Array.
Cols = Split(sColsList, ",")
cUpper = UBound(Cols)
Set ws = wb.Worksheets(sName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(sFirstRow, Cols(0)), rg)
Dim Data As Variant: ReDim Data(0 To cUpper)
For n = 0 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
Data(n) = rg.Offset(, cOffset).Value
Next n
' Write values from Lookup Column to Lookup Array of Result Array.
Cols = Split(dColsList, ",")
Set ws = wb.Worksheets(dName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(dFirstRow, Cols(0)), rg)
Dim Result As Variant: ReDim Result(0 To cUpper)
Result(0) = rg.Value
' Define the (remaining) Write Arrays of Result Array.
Dim ResultNew As Variant: ReDim ResultNew(1 To UBound(Result(0)), 1 To 1)
For n = 1 To cUpper
Result(n) = ResultNew
Next n
' Write values from Data Array to Write Arrays of Result Array.
Dim cIndex As Variant
Dim i As Long
For i = 1 To UBound(Result(0))
cIndex = Application.Match(Result(0)(i, 1), Data(0), 0)
If IsNumeric(cIndex) Then
For n = 1 To cUpper
Result(n)(i, 1) = Data(n)(cIndex, 1)
Next n
End If
Next i
' Write values from Write Arrays of Result Array to Destination Columns.
For n = 1 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
rg.Offset(, cOffset).Value = Result(n)
Next n
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