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
Related
I want to compare two set of rows, data from sheet "Calculated Structure" is compared against data from sheet "MAP" and if it is a match return the value in last column.
Based on other responses I have the following code which joins the rows into a string and then leverages dictionary to perform the compare. I am using dictionary to improve performance as I am comparing MAP and DATA that are both 50,000+ records.
Sub CheckRows()
Dim cl As Range
Dim Ws1 As Worksheet
Dim Ws2 As Worksheet
Dim Vlu As String
Dim VluD As String
Dim Lc As Long
Dim x As Long
Dim i As Long
Dim ArrRslt() As Variant
Dim iRw As Long
Dim LastRow As Long
Dim RngTrgt As Range
Dim dict As New Scripting.Dictionary
Set Ws1 = Sheets("Calculated Structure")
Set Ws2 = Sheets("Map")
Lc = Ws2.Cells(2, Columns.Count).End(xlToLeft).Column - 1
RsltCol = Ws1.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
For icntr = RsltCol To 1 Step -1
If Cells(1, icntr).value = "Calced Result" Then
Columns(icntr).Delete
End If
Next
LastCol = Ws1.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Cells(1, LastCol + 1).value = "Calced Result"
i = 1
x = 1
ReDim ArrRslt(0)
With CreateObject("scripting.dictionary")
dict.CompareMode = 1
For Each cl In Ws2.Range("A1", Ws2.Range("A" & Rows.Count).End(xlUp))
Vlu = Join(Application.Index(cl.RESIZE(, Lc).value, 1, 0), "|")
dict.Add key:=Vlu, Item:=(Ws2.Cells(i, Lc + 1))
i = i + 1
Next cl
For Each cl In Ws1.Range("B2", Ws1.Range("B" & Rows.Count).End(xlUp))
VluD = Join(Application.Index(cl.RESIZE(, Lc).value, 1, 0), "|")
If dict.Exists(VluD) Then
CResult = dict(VluD)
ArrRslt(x - 1) = CResult
Else
ArrRslt(x - 1) = "?"
End If
ReDim Preserve ArrRslt(0 To x)
x = x + 1
Next cl
End With
LastRow = Ws1.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
Set RngTrgt = Ws1.Range(Cells(2, Lc + 2), Cells(LastRow, Lc + 2))
RngTrgt = Application.WorksheetFunction.Transpose(ArrRslt)
MsgBox "Mapping Complete!"
End Sub
Some instances row on "MAP" have wildcard("*") designated as the cell value.
For example MAP looks like:
Check 1
Check 2
Check 3
Result
ABC
DEF
123
R1
ABC
*
123
R2
And my data looks like:
Field 1
Field 2
Field 3
Expected Result
ABC
DEF
123
R1
ABC
GHI
123
R2
For my second row of data I expect to return "R2" because in the MAP check 2 is wildcarded, so any value for Field 2 should pass. Instead the "?" is returned indicating no match found. My understanding is that this is because string "ABC|GHI|123" is not defined in the map.
What can I do to account for the wildcard values?
I feel I need to evaluate each "Check/Field" individually. Meaning first see if match found for Check 1, if so search for match for Check 2, so on till all matches are found.
I tried nested dictionaries. I believe that I am able to assign values appropriate but hitting a Run-time Error '450' when trying to retrieve information from the dictionaries. Here is my code:
Sub theDictionary()
Dim cl As Range
Dim WsRslt As Worksheet
Dim WsMap As Worksheet
Dim Vlu As String
Dim VluD As String
Dim Lc As Long
Dim MapVl As Long
Dim RsltCol As Long
Dim icntr As Long
Dim LastCol As Long
Dim rCount As Long
Dim mCount As Long
Dim x As Long
Dim i As Long
Dim dictCount As Long
Dim ArrRslt() As Variant
Dim iRw As Long
Dim LastRow As Long
Dim previousCell As String
Dim cResult As String
Dim RngTrgt As Range
Dim dict As New Scripting.Dictionary
Dim subDict() As Object
Set WsRslt = Sheets("Calculated Structure")
Set WsMap = Sheets("Map")
MapVl = WsMap.Cells(2, Columns.Count).End(xlToLeft).Column
Lc = MapVl - 1
MsgBox "Map value=" & MapVl & "and LC=" & Lc
RsltCol = WsRslt.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
For icntr = RsltCol To 1 Step -1
If Cells(1, icntr).value = "Calced Result" Then
Columns(icntr).Delete
End If
Next
LastCol = WsRslt.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Cells(1, LastCol + 1).value = "Calced Result"
i = 1
x = 1
dictCount = 1
rCount = 1
ReDim ArrRslt(0)
previousCell = ""
ReDim subDict(1 To MapVl - 1)
For dictCount = 1 To MapVl - 1
Set subDict(dictCount) = New Scripting.Dictionary
subDict(dictCount).CompareMode = vbTextCompare
Next dictCount
For Each cl In WsMap.Range("A2", WsMap.Range("A" & Rows.Count).End(xlUp))
For rCount = 1 To MapVl - 1
'get the first item and add it to the subdictionary
' MsgBox "MapVl=" & MapVl & " and rCount=" & rCount
If rCount = 1 Then
If subDict(rCount).Exists(cl.Offset(0, MapVl - (rCount + 1)).Value2) Then
' MsgBox cl.Offset(0, MapVl - (rCount + 1)) & " Exists"
Else
' MsgBox cl.Offset(0, MapVl - (rCount + 1)) & " First Add"
subDict(rCount).Add CStr(cl.Offset(0, MapVl - (rCount + 1)).Value2), CStr(cl.Offset(0, MapVl - rCount).Value2)
End If
ElseIf rCount < MapVl Then
subDict(rCount).Add CStr(cl.Offset(0, MapVl - (rCount + 1)).Value2), subDict(rCount - 1)
End If
' MsgBox "Prev Cell Blank=" & previousCell & "cl.value=" & cl.value
Next rCount
rCount = 1
Next cl
For Each cl In WsRslt.Range("B2", WsRslt.Range("B" & Rows.Count).End(xlUp))
For mCount = 1 To MapVl - 1
VluD = cl.Value2 'Join(Application.Index(cl.RESIZE(, Lc).value, 1, 0), "|")
MsgBox "Cell Value=" & VluD
If subDict(MapVl - mCount).Exists(VluD) Then
MsgBox "VluD= " & VluD
cResult = subDict(MapVl - mCount).Item(VluD) '<-- Run-time error '450': Wrong number of arguments or invalid property assignment
ArrRslt(x - 1) = cResult
MsgBox "cResult=" & cResult
Else
ArrRslt(x - 1) = "?"
End If
ReDim Preserve ArrRslt(0 To x)
x = x + 1
Next mCount
Next cl
LastRow = WsRslt.Cells.Find("*", LookIn:=xlFormulas, SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
Set RngTrgt = WsRslt.Range(Cells(2, Lc + 2), Cells(LastRow, Lc + 2))
RngTrgt = Application.WorksheetFunction.Transpose(ArrRslt)
MsgBox "Mapping Complete!"
End Sub
Once I am able to read the values, I still need to figure out how I am going to account for the "wildcard" values in MAP.
Any guidance is greatly appreciated.
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.
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 am trying to work through a code that utilizes a system to check two different worksheets by using a for loop and highlight the differences/edits made in the second sheet ("Version 2") onto the first sheet ("Original"). I have a feeling that I need to utilize an array but I'm not advanced enough where I know how to store the values and then later write them onto another sheet (down below).
I've gotten the code so that it highlights all the relevant cells, but now I'm trying to output it into a report (on another sheet called 'Logged Changes') which will summarize all the cell addresses where edits were made. Please forgive all the variables as this is from an old code set where variables are not explicitly defined:
Private Sub CompareBasic()
Dim actSheet As Range
Dim k As Integer
Dim o As Long
Dim p As Long
Dim i As Integer
Dim change As Integer
o = Worksheets("Original").Cells(2, Columns.Count).End(xlToLeft).Column
p = Worksheets("Original").Range("A" & Rows.Count).End(xlUp).Row
change = 0
Sheets("Original").Select
For i = 2 To p
For k = 1 To o
If IsNumeric(Worksheets("Original").Cells(i, k).Value) = True Then
If Worksheets("Original").Cells(i, k).Value <> Worksheets("Version 2").Cells(i, k).Value Then
Worksheets("Original").Cells(i, k).Interior.ColorIndex = 37
change = change + 1
End If
Else
If StrComp(Worksheets("Original").Cells(i, k), Worksheets("Version 2").Cells(i, k), vbBinaryCompare) <> 0 Then
Worksheets("Original").Cells(i, k).Interior.ColorIndex = 37
change = change + 1
End If
End If
Next k
Next i
Unload Me
MsgBox "Number of cells edited counted: " & change, vbOKOnly + vbExclamation, "Summary"
b = Empty
answer = MsgBox("Do you want to run the Report?", vbYesNo + vbQuestion)
If answer = vbYes Then
If Sheet_Exists("Logged Changes") = False Then
Sheet_Name = "Logged Changes"
Worksheets.Add(After:=Sheets(Sheets.Count)).Name = Sheet_Name
End If
Worksheets("Logged Changes").Range("A1") = "Edited Requirements"
Else
Unload Me
End If
End Sub
I have tried fiddling around with the code, but didn't want to clog it up with any unnecessary/broken lines. Any help would be greatly appreciated!
Try this:
Option Explicit
Private Sub CompareBasic()
Const SHT_REPORT As String = "Logged Changes"
Dim actSheet As Range
Dim c As Integer
Dim o As Long
Dim p As Long
Dim r As Long
Dim change As Long, wsOrig As Worksheet, wsNew As Worksheet, wsReport As Worksheet
Dim dataOrig, dataNew, rngData As Range, v1, v2, bDiff As Boolean
Dim arrUpdates
Set wsOrig = Worksheets("Original")
Set wsNew = Worksheets("Version 2")
o = wsOrig.Cells(2, Columns.Count).End(xlToLeft).Column
p = wsOrig.Range("A" & Rows.Count).End(xlUp).Row
Set rngData = wsOrig.Range("A2", wsOrig.Cells(p, o))
dataOrig = rngData.Value 'get an array of data
dataNew = wsNew.Range(rngData.Address).Value 'array of new data
ReDim arrUpdates(1 To rngData.Cells.Count, 1 To 3) 'for change info
change = 0
For r = 1 To UBound(dataOrig, 1)
For c = 1 To UBound(dataOrig, 2)
v1 = dataOrig(r, c)
v2 = dataNew(r, c)
If Len(v1) > 0 Or Len(v2) > 0 Then
If IsNumeric(v1) Then
bDiff = v1 <> v2
Else
bDiff = StrComp(v1, v2, vbBinaryCompare) <> 0
End If
End If
'any difference?
If bDiff Then
change = change + 1
With rngData.Cells(r, c)
arrUpdates(change, 1) = .Address
.Interior.ColorIndex = 37
End With
arrUpdates(change, 2) = v1
arrUpdates(change, 3) = v2
End If
Next c
Next r
If MsgBox("Do you want to run the Report?", vbYesNo + vbQuestion) = vbYes Then
With GetSheet(SHT_REPORT, ThisWorkbook)
.UsedRange.ClearContents
.Range("A1") = "Edited Requirements"
.Range("A3").Resize(1, 3).Value = Array("Address", wsOrig.Name, wsNew.Name)
.Range("A4").Resize(change, 3).Value = arrUpdates
End With
Else
'Unload Me
End If
End Sub
'return as sheet from wb by name (and create it if it doesn't exist)
Function GetSheet(wsName, wb As Workbook) As Worksheet
Dim rv As Worksheet
On Error Resume Next
Set rv = wb.Worksheets(wsName)
On Error GoTo 0
If rv Is Nothing Then
Set rv = wb.Worksheets.Add(After:=wb.Worksheets(wb.Worksheets.Count))
rv.Name = "Logged Changes"
End If
Set GetSheet = rv
End Function
Sheet Differences
Option Explicit
Sub logChanges()
Const ws1Name As String = "Original"
Const ws2Name As String = "Version 2"
Const wsResult As String = "Logged Changes"
Const FirstRow As Long = 2
Const FirstColumn As Long = 1
Const LastRowColumn As Long = 1
Const LastColumnRow As Long = 2
Const ResultFirstCell As String = "A2"
Dim Headers As Variant
Headers = Array("Id", "Address", "Original", "Version 2")
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(ws1Name)
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, LastRowColumn).End(xlUp).Row
Dim LastColumn As Long
LastColumn = ws.Cells(LastColumnRow, ws.Columns.Count) _
.End(xlToLeft).Column
Dim rng As Range
Set rng = ws.Range(ws.Cells(FirstRow, FirstColumn), _
ws.Cells(LastRow, LastColumn))
Dim Data1 As Variant: Data1 = rng.Value
Set ws = wb.Worksheets(ws2Name)
Dim Data2 As Variant: Data2 = ws.Range(rng.Address).Value
Dim Result() As Variant
Dim i As Long, j As Long, k As Long
For i = 1 To UBound(Data1)
For j = 1 To UBound(Data1, 2)
If Data1(i, j) <> Data2(i, j) Then GoSub writeResult
Next j
Next i
If k > 0 Then
transpose2D Result
On Error GoTo MissingResultSheet
Set ws = wb.Worksheets(wsResult)
On Error GoTo 0
ws.Range(ws.Range(ResultFirstCell), _
ws.Cells(ws.Rows.Count, ws.Columns.Count)).Clear
ws.Range(ResultFirstCell).Resize(k, UBound(Result, 2)).Value = Result
MsgBox "Found '" & k & "' difference(s) in range '" _
& rng.Address(False, False) & "'.", vbInformation
Else
MsgBox "Found no differences in range '" _
& rng.Address(False, False) & "'.", vbExclamation
End If
Exit Sub
writeResult:
k = k + 1
ReDim Preserve Result(1 To 4, 1 To k)
Result(1, k) = k
Result(2, k) = getAddress(i + FirstRow - 1, j + FirstColumn - 1)
Result(3, k) = Data1(i, j)
Result(4, k) = Data2(i, j)
Return
MissingResultSheet:
If Err.Number = 9 Then
wb.Worksheets.Add After:=wb.Sheets(wb.Sheets.Count)
With ActiveSheet
.Name = wsResult
If .Range(ResultFirstCell).Row > 1 Then
.Range(ResultFirstCell).Offset(-1) _
.Resize(, UBound(Headers) + 1).Value = Headers
End If
End With
Resume ' i.e. the code continues with Set ws = wb.Worksheets(wsResult)
Else
'?
Exit Sub
End If
End Sub
Function getAddress(aRow As Long, aColumn As Long) As String
getAddress = ActiveSheet.Cells(aRow, aColumn).Address(False, False)
End Function
Sub transpose2D(ByRef Data As Variant)
Dim i As Long, j As Long
Dim Result As Variant
ReDim Result(LBound(Data, 2) To UBound(Data, 2), _
LBound(Data) To UBound(Data))
For i = LBound(Data) To UBound(Data)
For j = LBound(Data, 2) To UBound(Data, 2)
Result(j, i) = Data(i, j)
Next j
Next i
Data = Result
End Sub
This solution for converting a column number to a string without using objects Function to convert column number to letter? could be used to write a descent getAddress function.
This program removes a string from an array to a new sheet. I find the string 'hello' or 'bye' but I also want the string in the index before each of those strings. The string before 'hello' or 'bye' will not always be the same so how can the I use the Index() function?
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
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("bye","hello")
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
While the InStr function is typically used to locate a substring within a string, your multiple search terms may be better handled with the Split function.
Option Explicit
Sub stripName()
Dim rw As Long
With ActiveSheet
For rw = 1 To .Cells(Rows.Count, "D").End(xlUp).Row
.Cells(rw, "A") = Split(Split(.Cells(rw, "D").Value2, ", hello")(0), ", bye")(0)
Next rw
End With
End Sub
Note that the search terms use on the split are case-sensitive.
Addendum for revised question:
Option Explicit
Sub stripName()
Dim rw As Long, s As String
With ActiveSheet
For rw = 1 To .Cells(Rows.Count, "D").End(xlUp).Row
s = Split(.Cells(rw, "D").Value2, ", bye")(0)
s = Split(s, ", hello")(0)
s = Split(Chr(32) & s, Chr(32))(UBound(Split(Chr(32) & s, Chr(32))))
.Cells(rw, "A") = s
Next rw
End With
End Sub