I want a cod that will find duplicates and return it in separate array.
So I found a code that would be perfect for me, but the thing is that this code is removing duplicates. I thought that it will be a simple job to change it, but somehow I cannot manage to do it....
I was thinking that it will be in this part of code If Err.Number <> 0 Then coll.Remove txt but have no idea how to change it. I have tried changing <> with = but it seems not to work.
Can someone tell me where and how should I change the code to get duplicates from 2 arrays.
Sub test()
Dim arr1 As Variant
Dim arr2 As Variant
Dim arr3 As Variant
Dim coll As Collection
Dim I As Long, j As Long, ii As Long, txt As String, x
With Worksheets("Sheet1")
LastRowColumnA = .Cells(.Rows.Count, 1).End(xlUp).Row
arr1 = .Range("A2:C" & LastRowColumnA).Value
End With
With Worksheets("Sheet2")
LastRowColumnA = .Cells(.Rows.Count, 1).End(xlUp).Row
arr2 = .Range("A2:C" & LastRowColumnA).Value
End With
Set coll = New Collection
On Error Resume Next
For I = LBound(arr1, 1) To UBound(arr1, 1)
txt = Join(Array(arr1(I, 1), arr1(I, 2), arr1(I, 3)), Chr(2))
coll.Add txt, txt
Next I
For I = LBound(arr2, 1) To UBound(arr2, 1)
txt = Join(Array(arr2(I, 1), arr2(I, 2), arr2(I, 3)), Chr(2))
Err.Clear
coll.Add txt, txt
If Err.Number <> 0 Then coll.Remove txt
Next I
ReDim arr3(1 To coll.Count, 1 To 3)
For I = 1 To coll.Count
x = Split(coll(I), Chr(2))
For ii = 0 To 2
arr3(I, ii + 1) = x(ii)
Next
Next I
Worksheets("test").Range("A2").Resize(UBound(arr3, 1), 3).Value = arr3
Columns("A:C").EntireColumn.AutoFit
End Sub
Regards,
Timonek
Extract Duplicates
If you set CountSameWorksheetDuplicates to True, it will return the duplicates of each worksheet even if they are not found in the other worksheet.
Option Explicit
Sub ExtractDuplicates()
Const sName1 As String = "Sheet1"
Const sCols1 As String = "A:C"
Const sfRow1 As Long = 2
Const sName2 As String = "Sheet2"
Const sCols2 As String = "A:C"
Const sfRow2 As Long = 2
Const dName As String = "Test"
Const dfCellAddress As String = "A2"
Const CountSameWorksheetDuplicates As Boolean = False
Dim Delimiter As String: Delimiter = Chr(2)
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sData As Variant
sData = RefColumns(wb.Worksheets(sName1).Rows(sfRow1).Columns(sCols1))
Dim cCount As Long: cCount = UBound(sData, 2)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim r As Long
Dim sKey As Variant
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
If CountSameWorksheetDuplicates Then
DictAddCount dict, sKey
Else
DictAdd dict, sKey, 1
End If
Next r
sData = RefColumns(wb.Worksheets(sName2).Rows(sfRow2).Columns(sCols2))
If CountSameWorksheetDuplicates Then
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
DictAddCount dict, sKey
Next r
Else
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
dict2.CompareMode = vbTextCompare
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
DictAdd dict2, sKey
Next r
For Each sKey In dict2.Keys
DictAddCount dict, sKey
Next sKey
Set dict2 = Nothing
End If
Erase sData
For Each sKey In dict.Keys
If dict(sKey) = 1 Then dict.Remove sKey
Next sKey
Dim drCount As Long: drCount = dict.Count
If drCount = 0 Then Exit Sub
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
r = 0
Dim c As Long
For Each sKey In dict.Keys
sData = Split(sKey, Delimiter)
r = r + 1
For c = 1 To cCount
dData(r, c) = sData(c - 1)
Next c
Next sKey
Dim drg As Range
Set drg = wb.Worksheets(dName).Range(dfCellAddress).Resize(drCount, cCount)
drg.Value = dData
drg.Resize(drg.Worksheet.Rows.Count - drg.Row - drCount + 1) _
.Offset(drCount).Clear ' clear below
drg.EntireColumn.AutoFit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the range from the first row of a range
' ('FirstRowRange') to the row range containing
' the bottom-most non-empty cell in the row's columns.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumns( _
ByVal FirstRowRange As Range) _
As Range
If FirstRowRange Is Nothing Then Exit Function
With FirstRowRange.Rows(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function ' empty range
Set RefColumns = .Resize(lCell.Row - .Row + 1)
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a row of a 2D array in a delimited string.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function StrJoinedDataRow( _
ByVal Data As Variant, _
ByVal RowIndex As Long, _
Optional ByVal Delimiter As String = " ") _
As String
Const ProcName As String = "StrJoinedDataRow"
On Error GoTo ClearError
Dim c As Long
Dim cString As String
For c = LBound(Data, 2) To UBound(Data, 2)
cString = cString & CStr(Data(RowIndex, c)) & Delimiter
Next c
StrJoinedDataRow = Left(cString, Len(cString) - Len(Delimiter))
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds a value ('Key') to a key of an existing ('ByRef')
' dictionary ('dict') adding another value ('Item')
' to the key's associated item.
' Remarks: Error and blank values are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DictAdd( _
ByRef dict As Object, _
ByVal Key As Variant, _
Optional ByVal Item As Variant = Empty)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Item
End If
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds a value ('Key') to a key of an existing ('ByRef')
' dictionary ('dict') increasing its count being held
' in the key's associated item.
' Remarks: Error and blank values are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DictAddCount( _
ByRef dict As Object, _
ByVal Key As Variant)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = dict(Key) + 1
End If
End If
End Sub
Dim Dict as Object
Dict = CreateObject("Scripting.Dictionary")
Dim Line As Object
For Each line in MyArray
On Error Resume Next
Dict.Add(Line, "")
On Error Goto 0
Next
Dictionaries don't allow duplicate keys. We are only setting keys and ignoring the value by not setting it. The dictionary raises an error if the key exists.
Related
I have a dict made up of multiple items (dates) per key, looks like this
Key Items dates()
23359720 03/12/2020 , 04/12/2020, 05/12/2020
23293711 26/01/2021
How can I transfer each key & item into an array (including repeating keys for multiple items)? so that array:
23359720 03/12/2020
23359720 04/12/2020
... ....
This is the code I have:
Sub Test_Dates()
'
Dim TESTWB As Workbook
Dim TESTWS As Worksheet
Set TESTWB = ThisWorkbook
Set TESTWS = TESTWB.Worksheets("TEST")
Dim Dict As New Scripting.Dictionary
For i = 2 To TESTWS.Cells(1, 1).End(xlDown).Row
Dict.Add TESTWS.Cells(i, 1).Value, getDates(TESTWS.Cells(i, 2), TESTWS.Cells(i, 3))
Next i
For i = 0 To Dict.Count - 1
Dim DateItem As Variant
For Each DateItem In Dict.Items(i)
Debug.Print Dict.Keys(i), DateItem
Next DateItem
Next i
'Dict to Array
Dim PricingDatesArr As Variant
PricingDatesArr = Dict.Keys
PricingDatesArr = Dict.Items
End Sub
This is to get dates between 2 sets of dates (i,2) (i,3) in the code
Function getDates(ByVal StartDate As Date, ByVal EndDate As Date) As Variant
Dim varDates() As Date
Dim lngDateCounter As Long
ReDim varDates(0 To CLng(EndDate) - CLng(StartDate))
For lngDateCounter = LBound(varDates) To UBound(varDates)
varDates(lngDateCounter) = CDate(StartDate)
StartDate = CDate(CDbl(StartDate) + 1)
Next lngDateCounter
getDates = varDates
End Function
This is the third question you have posted in as many days regarding Dictionaries. You might want to take a little time out a read up on Dictionary data structures a bit more.
The code below will return a 1 to n,by 1 to 2 array from a dictionary with arrays of dates.
Option Explicit
Public Function UnpackDictionaryOfArrays(ByRef ipDict As Scripting.DIctionary) As Variant
Dim myKeys As Collection
Set myKeys = New Collection
Dim myItems As Collection
Set myItems = New Collection
Dim myKey As Variant
Dim myItem As Variant
For Each myKey In ipDict
myItem = ipDict(myKey)
If IsArray(myItem) Then
Dim myElement As Variant
For Each myElement In myItems
myKeys.Add myKey
myItems.Add myElement
Next
Else
myKeys.Add myKey
myItems.Add myItem
End If
Next
' Now compile into a single array
Dim myKeyItem As Variant
ReDim myKeyItem(1 To myKeys.Count, 1 To 2)
Dim myIndex As Long
For myIndex = 1 To myKeys.Count
myKeyItem(myIndex, 1) = myKeys(myIndex)
myKeyItem(myIndex, 2) = myItems(myIndex)
Next
UnpackDictionaryOfArrays = myKeyIndex
End Function
Dictionary With Arrays to 2D One-Based Array
' Dict to Array
' Count the number of dates.
Dim Arr As Variant
Dim pCount As Long
For Each Arr In Dict.Items
pCount = pCount + UBound(Arr) - LBound(Arr) + 1
Next Arr
' Resize the array to the number of dates.
Dim PricingDatesArr As Variant: ReDim PricingDatesArr(1 To pCount, 1 To 2)
' Write the data to the array.
Dim Key As Variant
Dim n As Long, r As Long
For Each Key In Dict.Keys
For n = LBound(Dict(Key)) To UBound(Dict(Key))
r = r + 1
PricingDatesArr(r, 1) = Key
PricingDatesArr(r, 2) = Dict(Key)(n)
Next n
Next Key
' Print the elements of the array.
For r = 1 To pCount
Debug.Print PricingDatesArr(r, 1), PricingDatesArr(r, 2)
Next r
' Copy to a range.
'SheetX.Range("A1").Resize(pCount, 2).Value = PricingDatesArr
EDIT: The Function Edition
Usage in Your Code
' Dict to Array
Dim PricingDatesArr As Variant
PricingDatesArr = UnpivotDictionaryArrays(dict)
If IsEmpty(PricingDatesArr) Then Exit Sub
' Print the elements of the array.
For r = 1 To pCount
Debug.Print PricingDatesArr(r, 1), PricingDatesArr(r, 2)
Next r
' Copy to a range.
'SheetX.Range("A1").Resize(pCount, 2).Value = PricingDatesArr
The Function
Function UnpivotDictionaryArrays( _
ByVal dict As Object) _
As Variant
Const ProcName As String = "UnpivotDictionaryArrays"
On Error GoTo ClearError
' Count the items of the source (dictionary) arrays.
Dim Item As Variant
Dim n As Long
For Each Item In dict.Items
n = n + UBound(Item) - LBound(Item) + 1
Next Item
' Write the values from the dictionary to the destination array.
Dim Data As Variant: ReDim Data(1 To n, 1 To 2)
Dim r As Long
For Each Item In dict.Keys
For n = LBound(dict(Item)) To UBound(dict(Item))
r = r + 1: Data(r, 1) = Item: Data(r, 2) = dict(Item)(n)
Next n
Next Item
UnpivotDictionaryArrays = Data
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
IS it possible to look for an array of strings and/or integers inside an array of strings and/or integers? If so, then how?
To find a string in an array of strings I use code like:
If IsInArray(LowerFilmWidthArray, LowerFilmWidth) then
'Dos tuff
end if
And a function is:
Function IsInArray(arr As Variant, myVal As Variant) As Boolean
IsInArray = Not IsError(Application.Match(myVal, arr, 0))
Debug.Print (IsInArray)
End Function
As a result example, imagine you have an array of integers (1-10) and You are looking if your array (1,5,6) are inside that previous array (all items of it) and then return True. In my case I am getting all my to look for values in columns from 3rd to last column with data, which would make up my array that I try to find ALL items of in another array and return true or false.
An actual example:
Dim LowerFilmWidthArray
LowerFilmWidthArray = Application.Transpose(Evaluate("row(320:420)"))
Dim LowerFilmWidth As Integer
LowerFilmWidth = Array(ThisWorkbook.Worksheets("Machine Specification").Cells(320, 400,400,620)
'I get theese from a range and they might as well be strings and an undefined number of defined by 3 to last column with data
if isinarray(LowerFilmWidthArray,LowerFilmWidth) then
msgbox("Great Success!")
end if
Result in this one would be false because of that last "620" which is not inside the LowerFilmWidthArray.
EDITED:
Still can't get this to work and my gut says that there's way too many unnecessary things in the answers when I simply need to take each item from an array and try to find it in another and get "TRUE" only if all items I was looking for are present in a big array.
I have converted my to look for array (smaller one) to get the values from a set range that will always be a row from 3 to lastcolumn.
Dim LowerFilmWidth
LowerFilmWidth = ThisWorkbook.Worksheets("Machine Specification").Range(Cells(Cells.Find("Lower Film Width (mm)").Row, 3), Cells(Cells.Find("Lower Film Width (mm)").Row, LastColumn))
And I expect this part to make an array of all the values in cells in that range. Now I need to see if all those items / elements are present in:
Dim LowerFilmWidthArray
LowerFilmWidthArray = Application.Transpose(Evaluate("row(320:420)"))
So I use the suggested function:
Function arrElemInArray(arr As Variant, arrX As Variant) As Boolean
Dim i As Long, j As Long, boolFound As Boolean
For i = LBound(arrX) To UBound(arrX)
For j = LBound(arr) To UBound(arr)
If CStr(arr(j)) = CStr(arrX(i)) Then
boolFound = True: Exit For
End If
If Not boolFound Then arrElemInArray = False: Exit Function
Next j
Next i
arrElemInArray = True
Debug.Print (arrElemInArray)
End Function
and engage it using
If arrElemInArray(LowerFilmWidthArray, LowerFilmWidth) Then
msgbox("Great success!")
End If
The solution has to work both with integers and strings. I still can't get it to work as expected. Often it returns "True" no matter what, but it seems that it only checks the first item in smaller array against the big array.
This code in the edit returns "subscript out of range" error on "CStr(arrX(i))".
But the values in the sheet are as in the image
The full subroutine looks like this:
Sub Testing()
Dim LastColumn As Long
LastColumn = Cells(Cells.Find("Parameters", lookat:=xlWhole).Row, Columns.Count).End(xlToLeft).Column
Dim LowerFilmWidth
LowerFilmWidth = ThisWorkbook.Worksheets("Machine Specification").Range(Cells(Cells.Find("Lower Film Width (mm)").Row, 3), Cells(Cells.Find("Lower Film Width (mm)").Row, LastColumn))
Dim LowerFilmWidthArray
LowerFilmWidthArray = Application.Transpose(Evaluate("row(320:420)"))
If arrElemInArray(LowerFilmWidthArray, LowerFilmWidth) Then
MsgBox ("Great success!")
End If
End Sub
Workbook:
enter link description here
Please, look at the next example. Is this what you try accomplishing?
Sub testArrInArr()
Dim arr(), arr1(), arr2(), arr3(), arr4()
arr1 = Array(1, 2, 3): arr2 = Array(2, 3, 4)
arr3 = Array(3, 6, 5, 4): arr4 = Array(4, 5, 6)
arr = Array(arr1, arr2, arr3)
Debug.Print arrIsInArray(arr, arr2)
End Sub
Function arrIsInArray(arr As Variant, arrX As Variant) As Boolean
Dim i As Long, jArr As String
For i = LBound(arr) To UBound(arr)
If Join(arr(i)) = Join(arrX) Then arrIsInArray = True: Exit Function
Next i
End Function
Edited:
In order to test each array element if exists in another array, plese try the next way:
Sub tst2CheckArrElements()
Dim arr, arr1, arr2
arr = Split("1,2,3,4,5", ","): arr1 = Split("Sausage,Dog,Ship", ","): arr2 = Split("1,3,2", ",")
Debug.Print arrElemInArray(arr, arr1)
Debug.Print arrElemInArray(arr, arr2)
End Sub
Function arrElemInArray(arr As Variant, arrX As Variant) As Boolean
Dim i As Long, j As Long, boolFound As Boolean, mtch
If Not IsArray(arrX) Then
For j = LBound(arr) To UBound(arr)
If CStr(arr(j)) = CStr(arrX) Then arrElemInArray = True: Exit For
Next j
Exit Function
End If
For i = LBound(arrX) To UBound(arrX, 2)
For j = LBound(arr) To UBound(arr)
If CStr(arr(j)) = CStr(arrX(1, i)) Then
boolFound = True: Exit For
End If
Next j
If Not boolFound Then arrElemInArray = False: Exit Function
boolFound = False
Next i
arrElemInArray = True
End Function
Is Array In Array
Personalized Study
Change the number format of the cells containing the values to general or to a numeric format to make it work.
Option Explicit
Sub Testing()
Const sHeader As String = "Parameters"
Const sProperty As String = "Lower Film Width (mm)"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Machine Specification")
' Reference the last cell of the used range.
Dim LastCell As Range
With ws.UsedRange
Set LastCell = .Cells(.Rows.Count, .Columns.Count)
Debug.Print "UsedRange: " & .Address(0, 0)
Debug.Print "LastCell: " & LastCell.Address(0, 0)
End With
' Reference the header cell.
Dim HeaderCell As Range
Set HeaderCell = ws.Cells.Find(sHeader, LastCell, xlFormulas, xlWhole)
If HeaderCell Is Nothing Then Exit Sub ' header not found
Debug.Print "HeaderCell: " & HeaderCell.Address(0, 0)
' Calculate the first column number.
Dim FirstColumn As Long: FirstColumn = HeaderCell.Column + 1
Debug.Print "FirstColumn: " & FirstColumn
' Calculate the last column number.
Dim LastColumn As Long: LastColumn = _
ws.Cells(HeaderCell.Row, ws.Columns.Count).End(xlToLeft).Column
If LastColumn < FirstColumn Then Exit Sub ' no data to the right of header
Debug.Print "LastColumn: " & LastColumn
' Reference the column range below the header cell
' to search for the property.
Dim sDataColumnRange As Range ' below the header
Set sDataColumnRange _
= HeaderCell.Resize(LastCell.Row - HeaderCell.Row).Offset(1)
Debug.Print "sDataColumnRange: " & sDataColumnRange.Address(0, 0); ""
' Reference the property cell.
Dim sPropertyCell As Range
With sDataColumnRange
Set sPropertyCell _
= .Find(sProperty, .Cells(.Rows.Count), xlFormulas, xlWhole)
If sPropertyCell Is Nothing Then Exit Sub ' property not found
Debug.Print "sPropertyCell: " & sPropertyCell.Address(0, 0)
End With
' Reference the property (values) row range (first to last column).
Dim PropertyRowRange As Range
Set PropertyRowRange = ws.Range(ws.Cells(sPropertyCell.Row, FirstColumn), _
ws.Cells(sPropertyCell.Row, LastColumn))
Debug.Print "PropertyRowRange: " & PropertyRowRange.Address(0, 0)
Debug.Print "PropertyRowRange Values" & vbLf & Join(Application.Transpose( _
Application.Transpose(PropertyRowRange.Value)), ", ")
' Populate the property values array.
Dim PropertyValuesArray As Variant
PropertyValuesArray = Application.Transpose(Evaluate("Row(320:420)"))
Debug.Print "PropertyValuesArray Values"
Debug.Print Join(PropertyValuesArray, ", ")
' Return the result whether all values of the property row range
' are found in the property values array.
If IsRowInArr(PropertyValuesArray, PropertyRowRange) Then
MsgBox "All matching.", vbInformation
Debug.Print "All matching."
Else
MsgBox "Not all matching.", vbCritical
Debug.Print "Not all matching."
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a boolean indicating whether a 1D array ('InArr')
' contains all values in a row ('RowIndex')
' of a range ('IsRange').
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function IsRowInArr( _
ByVal InArr As Variant, _
ByVal IsRange As Range, _
Optional ByVal RowIndex As Long = 1) _
As Boolean
Const ProcName As String = "IsRowInArr"
On Error GoTo ClearError
With IsRange.Rows(RowIndex)
Dim cCount As Long: cCount = .Columns.Count
If cCount = 1 Then
IsRowInArr = IsNumeric(Application.Match(.Value, InArr, 0))
Else
Dim IsRow As Variant: IsRow = .Value
IsRowInArr = Application.Count(Application.Match( _
IsRow, InArr, 0)) = cCount
End If
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
Initial Answer
The function will return true if all the elements of an array (IsArr) are found in another array (InArr).
Option Explicit
Sub IsArrayInArrayTEST()
Dim InArr As Variant: InArr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Dim IsArr As Variant
IsArr = Array(1)
Debug.Print IsArrayInArray(IsArr, InArr) ' True
IsArr = Array(1, 5, 11)
Debug.Print IsArrayInArray(IsArr, InArr) ' False
End Sub
Function IsArrayInArray( _
ByVal IsArr As Variant, _
ByVal InArr As Variant) _
As Boolean
Dim IsCount As Long: IsCount = UBound(IsArr) - LBound(IsArr) + 1
Dim rArr As Variant: rArr = Application.Match(IsArr, InArr, 0)
Dim rCount As Long: rCount = Application.Count(rArr)
'Debug.Print rCount, IsCount
If rCount = IsCount Then
IsArrayInArray = True
End If
End Function
I have several excel sheets with tables with a similar format to below:
Where I'd like to copy all the rows where the number under the "Value" header exceeds 10 into a blank tab.
Sub Copy_Criteria()
With Range("A5:P1000")
.AutoFilter Field:=15, Criteria1:=">10"
End With
End Sub
After that I would like to select all the values filtered here and copy them into a blank sheet. Next I'd like to repeat the whole process, but copying the rows based on another header/criteria into a second blank tab.
Thanks!
You can do something like this:
With Range("A5:P1000")
.AutoFilter Field:=15, Criteria1:=">30"
On Error Resume Next 'in case no visible cells
.SpecialCells(xlCellTypeVisible).Copy Sheet2.Range("a1")
On Error Goto 0
.Parent.ShowAllData 'clear filter
End With
Copy By Criteria
Adjust the values in the constants section.
Option Explicit
Sub CopyByCriteria()
' Needs 'RefCurrentRegionBottomRight', 'GetFilteredRange' and 'GetRange'.
Const ProcTitle As String = "CopyByCriteria"
Const sFirst As String = "A5"
Dim swsNames As Variant: swsNames = Array("Sheet1", "Sheet2", "Sheet3")
Const dFirst As String = "A1"
' These three arrays need to have the same number of elements.
Dim dwsNames As Variant: dwsNames = Array("15gt10", "12gt15")
Dim dFields As Variant: dFields = Array(15, 12)
Dim dCriteria As Variant: dCriteria = Array(">10", ">15")
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet
Dim srg As Range
Dim dws As Worksheet
Dim drg As Range
Dim dCell As Range
Dim dData As Variant
Dim n As Long
Dim IncludeHeaders As Boolean
For n = LBound(dwsNames) To UBound(dwsNames)
On Error Resume Next
Application.DisplayAlerts = False
wb.Sheets(dwsNames(n)).Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set dws = wb.Worksheets.Add(After:=wb.Sheets(wb.Sheets.Count))
dws.Name = dwsNames(n)
Set dCell = dws.Range(dFirst)
IncludeHeaders = True
For Each sws In wb.Worksheets(swsNames)
Set srg = RefCurrentRegionBottomRight(sws.Range(sFirst))
dData = GetFilteredRange( _
srg, dFields(n), dCriteria(n), IncludeHeaders, IncludeHeaders)
If Not IsEmpty(dData) Then
IncludeHeaders = False ' include only the first time
Set drg = dCell.Resize(UBound(dData, 1), UBound(dData, 2))
drg.Value = dData
Set dCell = dCell.Offset(UBound(dData, 1))
End If
Next sws
Next n
MsgBox "Worksheets created. Values copied.", vbInformation, ProcTitle
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the filtered 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 GetFilteredRange( _
ByVal rg As Range, _
ByVal fField As Long, _
ByVal fCriteria As String, _
Optional ByVal IncludeHeaders As Boolean = True, _
Optional ByVal AllowHeadersOnly As Boolean = False) _
As Variant
' Needs the 'GetRange' function.
Const ProcName As String = "GetFilteredRange"
On Error GoTo ClearError
Dim ws As Worksheet: Set ws = rg.Worksheet
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
rg.AutoFilter fField, fCriteria
Dim frShift As Long: frShift = IIf(IncludeHeaders, 0, 1)
Dim frg As Range
On Error Resume Next
Set frg = rg.Resize(rg.Rows.Count - frShift) _
.Offset(frShift).SpecialCells(xlCellTypeVisible)
On Error GoTo ClearError
ws.AutoFilterMode = False
If Not frg Is Nothing Then
Dim frCount As Long
frCount = Intersect(frg, ws.Columns(frg.Column)).Cells.Count
Dim doContinue As Boolean: doContinue = True
If frShift = 0 Then
If frCount = 1 Then
If Not AllowHeadersOnly Then
doContinue = False
End If
End If
End If
If doContinue Then
Dim fcCount As Long: fcCount = frg.Columns.Count
Dim dData As Variant: ReDim dData(1 To frCount, 1 To fcCount)
Dim tData As Variant
Dim arg As Range
Dim ar As Long
Dim c As Long
Dim dr As Long
Dim trCount As Long
For Each arg In frg.Areas
tData = GetRange(arg)
For ar = 1 To arg.Rows.Count
dr = dr + 1
For c = 1 To fcCount
dData(dr, c) = tData(ar, c)
Next c
Next ar
Next arg
GetFilteredRange = dData
'Else ' frShift = 0: frCount = 1: AllowHeadersOnly = False
End If
'Else ' no filtered range
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "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
If rg Is Nothing Then Exit Function
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
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a reference to the range starting with a given cell
' and ending with the last cell of its Current Region.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCurrentRegionBottomRight( _
ByVal FirstCellRange As Range) _
As Range
If FirstCellRange Is Nothing Then Exit Function
With FirstCellRange.Cells(1).CurrentRegion
Set RefCurrentRegionBottomRight = _
FirstCellRange.Resize(.Row + .Rows.Count - FirstCellRange.Row, _
.Column + .Columns.Count - FirstCellRange.Column)
End With
End Function
I've got a sub representing a commandbutton of my userform, this userform has the perpose of listing (in a listbox) all unique items found in a column of a two-dimensional array. At frst I would like to implant an extra variable to hold and thus represent the number of times the unique item appears in the array. Secondly I would like the (Unique) items listed as:
Unique item 1 (number of appearances).
Example 1 (23)
Example 2 (39)
Example 3 (101)
Example 4 (9)
...
Example n (#)
Here is the code, can some body help me out?
Private Sub CommandButton5_Click()
Dim ws As Worksheet
Dim dictUnq As Object
Dim UnqList() As String
Dim aData As Variant
Dim vData As Variant
Dim pData As Variant
Dim i As Variant
Dim PrintString1() As String
i = 1
Set ws = ActiveWorkbook.Sheets("Sheet3")
Set dictUnq = CreateObject("Scripting.Dictionary")
Application.ScreenUpdating = False
Application.EnableEvents = False
With ws.Range("G2", ws.Cells(ws.Rows.Count, "G").End(xlUp))
If .Row < 2 Then Exit Sub 'No data
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Value
Else
aData = .Value
End If
End With
SBI_Omschrijving.ListBox1.Clear
For Each vData In aData
If Len(vData) > 0 Then
If Not dictUnq.exists(vData) Then dictUnq.Add vData, vData
End If
Next vData
Debug.Print dictUnq(vData)
SBI_Omschrijving.ListBox1.List = dictUnq.keys
MsgBox "Unique findings: " & dictUnq.Count
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Use a dictionary to store the count? This demonstrates the principle. Note in your example I think you may only be adding one column G so I don't know of you intended more?
Sub test()
Dim myArray()
myArray = ActiveSheet.Range("A1").CurrentRegion.Value
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
For i = LBound(myArray, 1) To UBound(myArray, 1) 'Depending on column of interest. Loop that
If Not dict.Exists(myArray(i, 1)) Then
dict.Add myArray(i, 1), 1
Else
dict(myArray(i, 1)) = dict(myArray(i, 1)) + 1
End If
Next i
Dim key As Variant
For Each key In dict.keys
Debug.Print key & "(" & dict(key) & ")"
Next key
End Sub
Your example might be something like (can't test dictionary on a mac I'm afraid so coding in my head)
Sub test()
Dim aData()
Dim ws As Worksheet
Dim targetRange As Range
Dim lastRow As Long
Set ws = ActiveSheet
lastRow = ws.Cells(ws.Rows.Count, "G").End(xlUp).Row
If lastRow = 1 Then Exit Sub
Set targetRange = ws.Range("G2:G" & lastRow)
If targetRange.Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = targetRange.Value
Else
aData = targetRange.Value2
End If
Dim dictUnq As Object
Set dictUnq = CreateObject("Scripting.Dictionary")
Dim i As Long
For i = LBound(aData, 1) To UBound(aData, 1) 'Depending on column of interest. Loop that
If Not dictUnq.Exists(aData(i, 1)) Then
dictUnq.Add aData(i, 1), 1
Else
dictUnq(aData(i, 1)) = dictUnq(aData(i, 1)) + 1
End If
Next i
Dim key As Variant
For Each key In dictUnq.keys
Debug.Print key & "(" & dictUnq(key) & ")"
Next key
End Sub
another possibility
Option Explicit
Private Sub CommandButton5_Click()
Dim dictUnq As Object
Set dictUnq = CreateObject("Scripting.Dictionary")
Dim cell As Range
With ActiveWorkbook.Sheets("Sheet3")
For Each cell In .Range("G2", .Cells(.Rows.Count, "G").End(xlUp))
dictUnq(cell.Value) = dictUnq(cell.Value) + 1
Next
End With
If dictUnq.Count = 0 Then Exit Sub
Dim key As Variant
With SBI_Omschrijving.ListBox1
.Clear
.ColumnCount = 2
For Each key In dictUnq.keys
.AddItem key
.List(.ListCount - 1, 1) = dictUnq(key)
Next
End With
MsgBox "Unique findings: " & dictUnq.Count
End Sub
The code below break the cells in image 1 into an array pictured in image 2. The new array is moved to start at AG. After that the program looks through the array and finds the words 'hello' and 'bye'. It takes those words and moves them into a new sheet and column pictured in image 3. Where I'm having trouble is that I want to still pull the strings 'hello' and 'bye' but I want to also pull the string directly before it from the array. In my example (image 3) I would've wanted it to read 'John Hello' instead of 'hello' on its own. What function would I use to extract the string before 'hello' or 'bye' also from the array?
Sub SplitWithFormat()
Dim R As Range, C As Range
Dim i As Long, V As Variant
Dim varHorizArray As Variant
Dim rge As Range
Dim intCol As Integer
Dim s As String
Set R = Range("d1", Cells(Rows.Count, "d").End(xlUp))
For Each C In R
With C
.TextToColumns Destination:=.Range("AD1"), DataType:=xlDelimited, _
consecutivedelimiter:=True, Tab:=False, semicolon:=True, comma:=False, _
Space:=True, other:=True, Otherchar:=vbLf
Set rge = Selection
varHorizArray = rge
.Copy
Range(.Range("AD1"), Cells(.Row, Columns.Count).End(xlToLeft)).PasteSpecial xlPasteFormats
End With
Next C
Application.CutCopyMode = False
For intCol = LBound(varHorizArray, 2) To UBound(varHorizArray, 2)
Debug.Print varHorizArray(1, intCol)
Next intCol
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
varHorizArray = Array("hello", "bye")
Set NewSh = Worksheets.Add
With Sheets("Sheet2").Range("AD1:AZ100")
Rcount = 0
For i = LBound(varHorizArray) To UBound(varHorizArray)
Set Rng = .find(What:=varHorizArray(i), _
After:=.Cells(.Cells.Count), _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
FirstAddress = Rng.Address
Do
Rcount = Rcount + 1
Rng.Copy NewSh.Range("A" & Rcount)
NewSh.Range("A" & Rcount).Value = Rng.Value
Set Rng = .FindNext(Rng)
Loop While Not Rng Is Nothing And Rng.Address <> FirstAddress
End If
Next i
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Option Explicit
Sub Tester()
Dim c As Range, v As String, arr, x As Long, e
Dim d As Range
'EDIT: changed destination for results
Set d = WorkSheets("Sheet2").Range("D2") '<<results start here
For Each c In ActiveSheet.Range("A2:A10")
v = Trim(c.Value)
If Len(v) > 0 Then
'normalize other separators to spaces
v = Replace(v, vbLf, " ")
'remove double spaces
Do While InStr(v, " ") > 0
v = Replace(v, " ", " ")
Loop
'split to array
arr = Split(v, " ")
For x = LBound(arr) To UBound(arr)
e = arr(x)
'see if array element is a word of interest
If Not IsError(Application.Match(LCase(e), Array("hello", "bye"), 0)) Then
If x > LBound(arr) Then
d.Value = arr(x - 1) & " " & e 'prepend previous word
Else
d.Value = "??? " & e 'no previous word
End If
Set d = d.Offset(1, 0)
End If
Next x
End If
Next c
End Sub
Something like this?
Option Explicit
Sub strings()
Dim ws As Worksheet
Dim rng As Range
Dim cell As Range
Dim lookingForThese() As String
Set ws = ThisWorkbook.Worksheets(1)
Set rng = ws.Range(ws.Range("A1"), ws.Range("A1").End(xlDown))
ReDim lookingForThese(1 To 2)
lookingForThese(1) = "bye"
lookingForThese(2) = "hello"
For Each cell In rng
Dim i As Integer
Dim parts() As String
'Split the string in the cell
parts = Split(cell.Value, " ")
'I'm parsing the parts to a 2. worksheet and the hello/bye + the word before those on a 3.
For i = LBound(parts) To UBound(parts)
Dim j As Integer
ThisWorkbook.Worksheets(2).Cells(cell.Row, i + 1).Value = parts(i)
For j = LBound(lookingForThese) To UBound(lookingForThese)
If parts(i) = lookingForThese(j) Then
If i <> LBound(parts) Then
ThisWorkbook.Worksheets(3).Cells(cell.Row, 1).Value = parts(i - 1) & " " & parts(i)
Else
ThisWorkbook.Worksheets(3).Cells(cell.Row, 1).Value = parts(i)
End If
End If
Next j
Next i
Next cell
End Sub