I am using a VBA script to find the 1's in a 15x15 array. The script finds the row,column address of each cell containing a 1 and maps it to a new (row,column) address based on a mapping table. Below is the excel file:
And this is the script I'm using to perform this mapping action:
Option Explicit
Sub findvalues()
Dim OldRow As Long, OldCol As Long, NewCol As Long, NewRow As Long, OldRowMapped As Long, OldColMapped As Long
Dim oldmappingrow As Variant, oldmappingcol As Variant, c As Range, firstAddress As String, cellAddress As String
With Worksheets(1).Range("a1:o15")
Set c = .Find(1, LookIn:=xlValues)
If Not c Is Nothing Then
firstAddress = c.Address
Do
cellAddress = c.Address
OldRow = Range(cellAddress).Row
OldCol = Range(cellAddress).Column
oldmappingrow = Application.Match(OldRow, Worksheets(1).Range("r3:r16"), 0)
If Not IsError(oldmappingrow) Then
OldRowMapped = Worksheets(1).Range("r3:r16").Cells(oldmappingrow).Offset(, 1).Value
End If
oldmappingcol = Application.Match(OldCol, Worksheets(1).Range("r3:r16"), 0)
If Not IsError(oldmappingcol) Then
OldColMapped = Worksheets(1).Range("r3:r16").Cells(oldmappingcol).Offset(, 1).Value
End If
If OldCol > OldRow Then
NewCol = WorksheetFunction.Max(OldRowMapped, OldColMapped)
NewRow = WorksheetFunction.Min(OldRowMapped, OldColMapped)
Else
NewRow = WorksheetFunction.Max(OldRowMapped, OldColMapped)
NewCol = WorksheetFunction.Min(OldRowMapped, OldColMapped)
End If
.Cells(NewRow, NewCol) = .Cells(OldRow, OldCol).Value
.Cells(OldRow, OldCol).Value = "0"
Set c = .FindNext(c)
MsgBox (OldRow & OldCol & " moved to " & NewRow & NewCol)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
End Sub
I need to prevent the .FindNext() from mapping values which have already been mapped before.
I thought a good idea would be to store each of the NewRow, New Col values in an array and then each loop check that OldRow, OldCol does not equal any of the entries in the NewRow, NewCol array.
How do I create an array to store the mapped cell addresses and check against them each loop?
Related
I want to check if the current destination string is located within the destination search array once the origins match up. The outcome is supposed to be all flights between any originSearch city and destinationSearch city and the corresponding flight number
I was playing with a boolean that stores all the true matches but I got confused.
Sub Matches()
Dim nFlights As Integer
Dim origin() As String
'Dim isOwned() As Boolean
Dim flightNumber() As String
Dim destination() As String
Dim iOrigin As Integer
Dim iDestination As Integer
Dim iFlight As Integer
Dim nOrigins As Integer
Dim nDestinations As Integer
Dim originSearch() As String
Dim destinationSearch() As String
Dim i As Integer
Dim x As Integer
Dim m As Integer
With wsData.Range("A1")
nFlights = Range(.Offset(1, 0), .End(xlDown)).Rows.Count
ReDim origin(1 To nFlights)
ReDim flightNumber(1 To nFlights)
ReDim destination(1 To nFlights)
'ReDim isOwned(1 To nFlights)
'stores the origin column in an array
For iOrigin = 1 To nFlights
'isOwned(iOrigin) = False
origin(iOrigin) = .Offset(iOrigin, 0).Value
Next
'stores the destination column in an array
For iDestination = 1 To nFlights
'isOwned(iDestination) = False
destination(iDestination) = .Offset(iDestination, 1).Value
Next
'stores the flight column in an array
For iFlight = 1 To nFlights
'isOwned(iFlight) = False
flightNumber(iFlight) = .Offset(iFlight, 2).Value
Next
End With
With wsData.Range("E1")
nOrigins = Range(.Offset(1, 0), .End(xlDown)).Rows.Count
nDestinations = 4 'Range(.Offset(1, 1), .End(xlDown)).Rows.Count
ReDim originSearch(1 To nOrigins)
ReDim destinationSearch(1 To nDestinations)
For i = 1 To nOrigins
originSearch(i) = .Offset(i, 0).Value
For x = 1 To nDestinations
destinationSearch(x) = .Offset(x, 1).Value
For m = 1 To nFlights
If origin(m) = originSearch(i) And destination(m) = destinationSearch(x) Then
wsData.Range("H1").Offset(i, 0).Value = originSearch(i)
wsData.Range("H1").Offset(x, 1).Value = destinationSearch(x)
wsData.Range("H1").Offset(x, 2).Value = flightNumber(m)
End If
Next m
Next x
Next i
End With
End Sub
I think you can solve the problem with this formula:
=FILTER(AllFlights;IFNA(MATCH(AllFlights[Origin];DesiredOrigin;0)*MATCH(AllFlights[Destination];DesiredDestination;0);0);)
Here:
AllFlights is the name of a table with all possible flights;
DeiredOrigin is the name of a table with origins of interest;
DeiredDestination is the name of a table with destinations of interest;
Multiplication of Matches is the matrix equivalent of the OR operator.
p.s. Instead of IFNA we can use ISNUMBER:
=FILTER(AllFlights;ISNUMBER(MATCH(AllFlights[Origin];DesiredOrigin;0)*MATCH(AllFlights[Destination];DesiredDestination;0));)
There should only be a single nested for loop at the end there.
So for each origin_dest-pair search value,
you're searching each origin_dest-pair record value.
This adds all the flight numbers of matching scenarios into an array and then puts the flight numbers into the next available column.
Also Ranges are essentially Variant() arrays, so you can just assign one to the other, instead of iterating through each value.
Option Compare Text
Sub FindFlightNumbers()
Dim orig() As Variant: orig = Range("A2:A" & Range("A2").End(xlDown).Row)
Dim dest() As Variant: dest = Range("B2:B" & Range("B2").End(xlDown).Row)
Dim flight_nums() As Variant: flight_nums = Range("C2:C" & Range("C2").End(xlDown).Row)
'Turn 2-D arrays into 1-D arrays
orig = Application.Transpose(orig)
dest = Application.Transpose(dest)
flight_nums = Application.Transpose(flight_nums)
Dim orig_search As Range: Set orig_search = Range("E2:E" & Range("e2").End(xlDown).Row)
Dim search_cell As Range, i As Integer
For Each search_cell In orig_search
Dim match_numbers() As Variant
For i = 1 To UBound(orig)
If search_cell.Value = orig(i) And search_cell.Offset(0, 1).Value = dest(i) Then
'If its the first match, init the array
If (Not match_numbers) = -1 Then
ReDim Preserve match_numbers(0)
match_numbers(0) = flight_nums(i)
Else
'Otherwise increment the array
ReDim Preserve match_numbers(UBound(match_numbers) + 1)
match_numbers(UBound(match_numbers)) = flight_nums(i)
End If
End If
Next i
'If the array had found matches, store them; comma-delimited
If Not Not match_numbers Then
search_cell.Offset(0, 2).Value = Join(match_numbers, ",")
End If
Erase match_numbers
Next search_cell
End Sub
Here's an approach using Match() directly against the search values on the worksheet:
Sub Matches()
Dim data, m As Long, rngOrigin As Range, rngDest As Range, m As Long, i As Long
'one array of all data: origin|destination|flight#
data = wsdata.Range("A2", wsdata.Cells(Rows.Count, "C").End(xlUp))
'set search ranges
Set rngOrigins = wsdata.Range("E2", wsdata.Cells(Rows.Count, "E").End(xlUp))
Set rngDest = wsdata.Range("F2", wsdata.Cells(Rows.Count, "F").End(xlUp))
'loop all source data
For m = 1 To UBound(data, 1)
'check Match() against search ranges
If Not IsError(Application.Match(data(m, 1), rngOrigins, 0)) Then
If Not IsError(Application.Match(data(m, 2), rngDest, 0)) Then
i = i + 1
wsdata.Range("H1").Offset(i, 0).Resize(1, 3) = _
Array(data(m, 1), data(m, 2), data(m, 3))
End If
End If
Next m
End Sub
The purpose is to open a workbook from SharePoint, set the auto filter, copy filtered range into the existing sheet.
The two longest pieces are opening the workbook and pasting as values.
I want to store the filtered range in the array and then assign this array to the existing worksheet (instead of copy - paste).
I have another module from which I am running all the subs (this is one of them). In that module I am starting with the below.
Public Sub TurnOffFunctionality()
Application.Calculation = xlCalculationManual
Application.DisplayStatusBar = False
Application.EnableEvents = False
Application.ScreenUpdating = False
End Sub
Sub OpenWorkbookWithPopulation()
strFilePath = *Path to the SharePoint*
period = 202009
file = period & "_FR05_GRIR_Population"
strFileName = file & ".xlsb"
Set wbkopen = Workbooks.Open(strFilePath & strFileName, ReadOnly:=True, UpdateLinks:=False)
With Workbooks(file)
.Worksheets("ERP Extract").AutoFilterMode = False
.Worksheets("ERP Extract").Range("A1").AutoFilter
.Worksheets("ERP Extract").Range("A1").AutoFilter field:=17, Criteria1:="Trade"
.Worksheets("ERP Extract").Range("A1").AutoFilter field:=18, Criteria1:=">" & 90
.Worksheets("ERP Extract").AutoFilter.Range.Copy
cockpit = .Worksheets("Cockpit").Range("C6:C12").Value2
End With
With Workbooks("Master_Template_Working")
.Worksheets("Aged GRNI_Pop").Range("A1").PasteSpecial xlPasteValues
.Worksheets("Instructions").Range("C38:C44") = cockpit
End With
Workbooks(file).Close SaveChanges:=False
End Sub
You can try something like this (not tested):
With Workbooks(file)
With .Worksheets("ERP Extract")
.AutoFilterMode = False
Dim Data As Variant
' If this doesn't work, use another way.
Data = .Range("A1").CurrentRegion.Value
End With
cockpit = .Worksheets("Cockpit").Range("C6:C12").Value2
End With
Dim ColumnsCount As Long
ColumnsCount = UBound(Data, 2)
Dim i As Long ' Source Rows Counter
Dim j As Long ' Columns Counter
Dim k As Long ' Destination Rows Counter
k = 1 ' account for headers (i = 2 To ...)
For i = 2 To UBound(Data, 1)
If Data(i, 17) = "Trade" And Data(i, 18) > 90 Then
k = k + 1
For j = 1 To ColumnsCount
Data(k, j) = Data(i, j)
Next j
End If
Next i
With Workbooks("Master_Template_Working")
With .Worksheets("Aged GRNI_Pop").Range("A1")
.Resize(k, ColumnsCount).Value = Data
End With
.Worksheets("Instructions").Range("C38:C44") = cockpit
End With
I keep getting a runtime Error 424 when I try to access arrayCount.Length. I think this might have to do with the fact that arrayCount was declared as a Public Variant. How do I resolve this bug?
' Initialize variables
Private counter As Integer
Private Account As String
Private chartSize As Integer
Public arrayCount As Variant
Public arrayAccounts As Variant
' Iterate over each entry row, determining the corresponding Account
Sub RowInsert()
' Initialize ArrayCount with starting values of -1
arrayCount = Array(-1, -1, -1, -1, -1, -1, -1, -1, -1)
arrayAccounts = Array("Cash", "Equipment", "Prepaid Rent", "Inventory", "Marketable Securities", "Accounts Recievable", "Accounts Payable", "Bonds Payable", "Common Stock")
' BUG HERE
chartSize = arrayAccounts.Length
' Continued...
End Sub
'Continued...
I coerced my previous answer to use collections instead of a Dictionary and Arraylists; so that it would be Mac compatible.
Sub MacCompileData()
Application.ScreenUpdating = False
Dim lastRow As Long, x As Long
Dim data, Key
Dim r As Range
Dim cLedger As Collection, cList As Collection
Set cLedger = New Collection
With Worksheets("Journal")
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
For x = 2 To lastRow
Key = Trim(.Cells(x, 2))
On Error Resume Next
Set cList = cLedger(Key)
If Err.Number <> 0 Then
Set cList = New Collection
cLedger.Add cList, Key
End If
On Error GoTo 0
cLedger(Key).Add Array(.Cells(x, 1).Value, .Cells(x, 3).Value, .Cells(x, 4).Value)
Next
End With
With Worksheets("Ledger")
For Each r In .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
If r <> "" Then
On Error Resume Next
Key = Trim(r.Text)
data = getLedgerArray(cLedger(Key))
If Err.Number = 0 Then
Set list = cLedger(Key)
x = cLedger(Key).Count
With r.Offset(2).Resize(x, 3)
.Insert Shift:=xlDown, CopyOrigin:=r.Offset(1)
.Offset(-x).Value = data
.Offset(0, 1).Resize(1, 1).FormulaR1C1 = "=""Bal. "" & TEXT(SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1]),""$#,###"")"
r.Offset(1).EntireRow.Delete
End With
End If
On Error GoTo 0
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Function getLedgerArray(c As Collection)
Dim data
Dim x As Long
ReDim data(1 To c.Count, 1 To 3)
For x = 1 To c.Count
data(x, 1) = c(x)(0)
data(x, 2) = c(x)(1)
data(x, 3) = c(x)(2)
Next
getLedgerArray = data
End Function
As an alternate approach I compiled all the information using a Dictionary to group the data. Each key in the Dictionary has an ArrayList associated with it. Each element in the ArrayList is an 1 dimensional array of data that holds the Date, Debit and Credit information.
The Ledger is then searched for each Key in the Dictionary. If found the array that the Dictionary's ArrayList is extracted and transposed twice to convert it to a standard 2 dimensional array. The array is then inserted into worksheet.
Sub CompileData()
Application.ScreenUpdating = False
Dim x As Long
Dim Data, Key
Dim r As Range
Dim dLedger As Object, list As Object
Set dLedger = CreateObject("Scripting.Dictionary")
With Worksheets("Journal")
For x = 2 To .Range("B" & .Rows.Count).End(xlUp).Row
Key = Trim(.Cells(x, 2))
If Not dLedger.Exists(Key) Then
Set list = CreateObject("System.Collections.ArrayList")
dLedger.Add Key, list
End If
dLedger(Key).Add Array(.Cells(x, 1).Value, .Cells(x, 3).Value, .Cells(x, 4).Value)
Next
End With
With Worksheets("Ledger")
For Each Key In dLedger
Set r = Intersect(.Columns("A:C"), .UsedRange).Find(What:=Key)
If Not r Is Nothing Then
Set list = dLedger(Key)
Data = list.ToArray
Data = Application.Transpose(Data)
x = dLedger(Key).Count
With r.Offset(2).Resize(x, 3)
.Insert Shift:=xlDown, CopyOrigin:=r.Offset(1)
.Offset(-x).Value = Application.Transpose(Data)
.Offset(0, 1).Resize(1, 1).FormulaR1C1 = "=""Bal. "" & TEXT(SUM(R[-" & x & "]C:R[-1]C)-SUM(R[-" & x & "]C[1]:R[-1]C[1]),""$#,###"")"
r.Offset(1).EntireRow.Delete
End With
End If
Next
End With
Application.ScreenUpdating = True
End Sub
I am trying to pull strings from column A and move them to column B only if they don't already exist in column B. To do this, I wanted to make a list and scan all of column A with it, however, I'm not sure how to do that in VBA. In python I recall using something along the lines of
[If (x) not in (List)]
but that same approach isnt working for me in Excel.
Currently, I have the following
Sub GatherAll()
GL = List()
rwcnt = WorksheetFunction.CountA(Range("A:A"))
lastc = Cells(1, Columns.Count).End(xlToLeft).Column
Dim i As Long
For i = 2 To rwcnt
Cells(i, 1).Value = n
and I want to say something like
if n not in GL, GL.append(n)
continue
End Sub
If anyone could help me out, I would really appreciate it.
Try adapting the following code to your exact needs and see if it helps. If you need help, let us know.
Sub MoveUniqueEntries()
Dim oDict As Object
Dim rToMove As Range
Dim rDest As Range
Dim rLoop As Range
Set oDict = CreateObject("Scripting.Dictionary")
Set rToMove = Intersect(Sheet1.Range("A1").CurrentRegion, Sheet1.Columns(1))
Set rDest = Sheet1.Range("B1")
For Each rLoop In rToMove
If oDict.exists(rLoop.Value) Then
'Do nothing
Else
oDict.Add rLoop.Value, 0
rDest.Value = rLoop.Value
Set rDest = rDest.Offset(1)
End If
Next rLoop
End Sub
In your VBA IDE you will have to add a reference. On the tools pulldown menu select references. Then select "Microsoft ActiveX Data Objects 2.8 Library".
Dim rs As New ADODB.Recordset
Dim ws As Excel.Worksheet
Dim lRow As Long
Set ws = Application.ActiveSheet
'Add fields to your recordset for storing data. You can store sums here.
With rs
.Fields.Append "Row", adInteger
.Fields.Append "Value", adInteger
.Open
End With
lRow = 1
'Loop through and record what is in the first column
Do While lRow <= ws.UsedRange.Rows.count
rs.AddNew
rs.Fields("Row").Value = lRow
rs.Fields("Value").Value = ws.Range("A" & lRow).Value
rs.Update
lRow = lRow + 1
ws.Range("A" & lRow).Activate
Loop
'Now go through and list out the unique values in columnB.
lRow = 1
rs.Sort = "value"
Do While lRow <= ws.UsedRange.Rows.count
if rs.Fields("value").Value <> strLast then
ws.Range("B" & lRow).Value = rs.Fields("value").Value
lRow = lRow + 1
End if
strLast = rs.Fields("value").Value
Loop
Cross-platform version (but will be slow for large numbers of values):
Sub UniquesTester()
Dim v, u(), i As Long, n As Long
n = 0
v = Range(Range("A1"), Cells(Rows.Count, 1).End(xlUp)).Value
ReDim u(1 To UBound(v, 1))
For i = 1 To UBound(v, 1)
If IsError(Application.Match(v(i, 1), u, 0)) Then
n = n + 1
u(n) = v(i, 1)
End If
Next i
ReDim Preserve u(1 To n)
Range("c1").Resize(n, 1).Value = Application.Transpose(u)
End Sub
I have data in columns P,Q,R. I would like to filter through R, and make a new Worksheet for each unique item in Column R. This new worksheet will also bring along the associated values in P and Q.
Thus far I have learned how to filter the data in R and put the unique values into an array. For each value in the array I made a new sheet named Array1(i) because I am unable to convert the value into a string for some reason. How can I do this in an optimized fashion such that I create a new sheet for each unique value in R and bring along the values in the same rows in P and Q as well? Here is my code:
Also, how do I declare the array dynamically rather than hard coding 50? How can I use a dynamic range for column R?
Note the values in the array will be something like 6X985
Sub testarray()
Dim TestRg As Excel.Range
Dim Array1(50) As Variant
Dim SheetName As String
Dim i, j, k As Integer
i = 1
Set TestRg = Range("R1:R36879")
TestRg.AdvancedFilter Action:=xlFilterInPlace, Unique:=True
For Each c In TestRg.SpecialCells(xlCellTypeVisible)
Array1(i) = c.Value
'SheetName = CStr(c.Value)
Worksheets.Add.Name = i
i = i + 1
Next c
j = i - 1
i = 1
Worksheets("Sheet1").ShowAllData
For Each c In Range("S3:S" & j)
c.Value = Array1(i)
i = i + 1
Next c
k = 1
For Each d In Range("T3:T" & j)
d.Value = k
k = k + 1
Next d
End Sub
The code itself is kind of advanced, I added comments to assist with understanding. I hope it helps:
Sub tgr()
Dim wsData As Worksheet
Dim wsNew As Worksheet
Dim rngData As Range
Dim xlCalc As XlCalculation
Dim arrUnq() As Variant
Dim strSheetName As String
Dim UnqIndex As Long
Dim i As Long
Set wsData = Sheets("Sheet1")
Set rngData = wsData.Range("R1", wsData.Cells(Rows.Count, "R").End(xlUp))
'Disable application items to let code run faster
With Application
xlCalc = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
End With
'Re-enable all the application items just in case there's an error
On Error GoTo CleanExit
'Get the list of unique values from rngData, sorted alphabetically
'Put that list into the arrUnq array
With Sheets.Add
rngData.AdvancedFilter xlFilterCopy, , .Range("A1"), True
.UsedRange.Sort .UsedRange, xlAscending, Header:=xlYes
arrUnq = Application.Transpose(.Range("A2", .Cells(Rows.Count, "A").End(xlUp)).Value)
.Delete
End With
For UnqIndex = LBound(arrUnq) To UBound(arrUnq)
'Verify a valid worksheet name
strSheetName = arrUnq(UnqIndex)
For i = 1 To 7
strSheetName = Replace(strSheetName, Mid(":\/?*[]", i, 1), " ")
Next i
strSheetName = Trim(Left(WorksheetFunction.Trim(strSheetName), 31))
'Check if worksheet name already exists
If Not Evaluate("ISREF('" & strSheetName & "'!A1)") Then
'Sheet doesn't already exist, create sheet
Sheets.Add(After:=Sheets(Sheets.Count)).Name = strSheetName
End If
Set wsNew = Sheets(strSheetName)
wsNew.UsedRange.Clear
'Filter for the unique data
With rngData
.AutoFilter 1, arrUnq(UnqIndex)
'Copy the data from columns P:R to the new sheet
Intersect(wsData.Range("P:R"), .EntireRow).SpecialCells(xlCellTypeVisible).Copy wsNew.Range("A1")
End With
Next UnqIndex
rngData.AutoFilter 'Remove any remaining filters
CleanExit:
With Application
.Calculation = xlCalc
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
End With
If Err.Number <> 0 Then
MsgBox Err.Description, , "Error: " & Err.Number
Err.Clear
End If
End Sub