Use the result of a loop in another loop - vba

I want to loop each number in column A through column B.
If there is a match, that number to be looped through column C.
If there is a match, I want the results to be returned in columns E and F.
Each column will have a variable amount of rows. There can also be multiple results.
In my example the number 1 from column A is looped through column B. If a match is found 1 is now looped through column C. If there is a match then columns E and F = C and D.
Example

If your data is arranged like bellow picture,then the code would be like this.
Sub test()
Dim vDB1, vDB2, vDB3, vR()
Dim i As Long, j As Long, k As Long, n As Long
vDB1 = Range("a2", Range("a" & Rows.Count).End(xlUp))
vDB2 = Range("b2", Range("b" & Rows.Count).End(xlUp))
vDB3 = Range("c2", Range("d" & Rows.Count).End(xlUp))
Range("e2:f2").Resize(Rows.Count - 1) = Empty
For i = 1 To UBound(vDB1, 1)
For j = 1 To UBound(vDB2, 1)
If vDB1(i, 1) = vDB2(j, 1) Then
For k = 1 To UBound(vDB3, 1)
If vDB1(i, 1) = vDB3(k, 1) Then
n = n + 1
ReDim Preserve vR(1 To 2, 1 To n)
vR(1, n) = vDB3(k, 1)
vR(2, n) = vDB3(k, 2)
End If
Next
End If
Next j
Next i
If n > 0 Then
Range("e2").Resize(n, 2) = WorksheetFunction.Transpose(vR)
End If
End Sub

Here is a code that should do the job. But if Tab1/Tab2 values are not unique then it might do a lookup multiple times. For example if there was 1 in place of 9 in Tab 2 it would show the row with 1 twice in tab 4. If you want to avoid that you will need to modify my code
Set tab1_list = Sheets("sheet1").Range("B6:B10")
Set tab2_list = Sheets("sheet1").Range("C6:C10")
Set tab3_list_lookup = Sheets("sheet1").Range("E6:E10")
Set Tab3_List_value = Sheets("sheet1").Range("F6:F10")
Set output_location = Sheets("sheet1").Range("H6")
For Each cell1 In tab1_list
For Each cell2 In tab2_list
If cell1.Value = cell2.Value Then
For index_no = 1 To tab3_list_lookup.Cells.Count
If tab3_list_lookup.Cells(index_no).Value = cell2.Value Then
output_location.Value = tab3_list_lookup.Cells(index_no).Value
output_location.Offset(0, 1) = Tab3_List_value.Cells(index_no).Value
Set output_location = output_location.Offset(1, 0)
End If
Next index_no
End If
Next cell2
Next cell1

Related

How to remove duplicate rows from specific column using macro vba

I have a problem on how to remove rows with the same values for specific Columns in excel.
Please see screenshot below for my problem:
In row 4: FullName: A SeqNo:003
In row 5: FullName: A SeqNo:003
So row 4 and 5 has the same value so I want to remove one of them but I don't want to remove row automatically by random but in condition that only the latest date will remain.
Condition be like this:
This should be the correct output:
So how can I achieve this by macro vba?
Thanks in advance!
Try this.
Sub test()
Dim vDB, vR()
Dim X As New Collection
Dim Str As String
Dim i As Long, j As Long, r As Long, c As Integer
Dim n As Long
vDB = Range("a1").CurrentRegion
r = UBound(vDB, 1)
c = UBound(vDB, 2)
On Error Resume Next
For i = 1 To r
Str = vDB(i, 1) & vDB(i, 4)
Err.Clear
X.Add Str, Str
If Err.Number = 0 Then
n = n + 1
ReDim Preserve vR(1 To c, 1 To n)
For j = 1 To c
vR(j, n) = vDB(i, j)
Next j
End If
Next i
For i = 1 To n
For j = 1 To r
If vDB(j, 1) = vR(1, i) And vDB(j, 4) = vR(4, i) Then
If vDB(j, 5) >= vR(5, i) Then
vR(2, i) = vDB(j, 2)
vR(5, i) = vDB(j, 5)
End If
End If
Next j
Next i
Sheets.Add
Range("a1").Resize(n, c) = WorksheetFunction.Transpose(vR)
End Sub
Record a macro of you sorting by the date column newest to oldest, then using the remove duplicates feature on the three columns but untick the date column in the remove duplicates dialogue. By default Excel keeps the first duplicate it comes to so by sorting you will remove the rows with older dates.
You should be able to tidy up the recorded code yourself.

Auto scheduling

I am trying to make an auto scheduling program with an excel.
For example, each number is certain job assigned to the person given day.
1/2 1/3 1/4 1/5
Tom 1 2 2 ?
Justin 2 3 1 ?
Mary 3 3 ?
Sam 1 ?
Check O O X ? ## check is like =if(b2=c2,"O","X")
The things I want to make sure is every person is given a different job from yesterday.
My idea
while
randomly distribute jobs for 1/5
wend CheckCell = "O"
But I found that checking cell in the vba script doesn't work - the cell is not updated in each while loop.
Could you give me a little pointer for these kinds of program? Because I am new to vbaScript, any kinds of help would be appreciated.
Using VBA, I'm sure there are better ways to do this, but this will check the values from the penultimate column against values from last column and if they match it will write "O" to under the last column, else it will write "X":
Sub foo()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
'get the last row with data on Column A
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
counter = 0 'set counter
For i = 2 To LastRow 'loop through penultimate column and add values to array
If ws.Cells(i, LastCol - 1).Value <> "" Then
Values = Values & ws.Cells(i, LastCol - 1) & ","
End If
Next i
Values = Left(Values, Len(Values) - 1)
Values = Split(Values, ",") 'split values into array
For i = 2 To LastRow 'loop through last column and add values to array
If ws.Cells(i, LastCol).Value <> "" Then
ValuesCheck = ValuesCheck & ws.Cells(i, LastCol) & ","
End If
Next i
ValuesCheck = Left(ValuesCheck, Len(ValuesCheck) - 1)
ValuesCheck = Split(ValuesCheck, ",")
For y = LBound(Values) To UBound(Values) 'loop through both arrays to find all values match
For x = LBound(ValuesCheck) To UBound(ValuesCheck)
If Values(y) = ValuesCheck(x) Then counter = counter + 1
Next x
Next y
If counter = UBound(Values) + 1 Then 'if values match
ws.Cells(LastRow + 1, LastCol).Value = "O"
Else 'else write X
ws.Cells(LastRow + 1, LastCol).Value = "X"
End If
End Sub
just to clarify are you looking to implement the random number in the vba or the check.
To do the check the best way would be to set the area as a range and then check each using the cells(r,c) code, like below
Sub checker()
Dim rng As Range
Dim r As Integer, c As Integer
Set rng = Selection
For r = 1 To rng.Rows.Count
For c = 1 To rng.Columns.Count
If rng.Cells(r, c) = rng.Cells(r, c + 1) Then
rng.Cells(r, c).Interior.Color = RGB(255, 0, 0)
End If
Next c
Next r
End Sub
this macro with check the text you have selected for the issue and change the cell red if it matches the value to the right.
To make it work for you change set rng = selection to your range and change the rng.Cells(r, c).Interior.Color = RGB(255, 0, 0) to the action you want
A sligthly different approach than the other answers.
Add this function:
Function PickJob(AvailableJobs As String, AvoidJob As String)
Dim MaxTries As Integer
Dim RandomJob As String
Dim Jobs() As String
Jobs = Split(AvailableJobs, ",")
MaxTries = 100
Do
MaxTries = MaxTries - 1
If MaxTries = 0 Then
MsgBox "Could find fitting job"
End
End If
RandomJob = Jobs(Int((1 + UBound(Jobs)) * Rnd()))
Loop Until RandomJob <> AvoidJob
PickJob = RandomJob
End Function
And put this formula in your sheet
=PickJob("1,2,3",D2)
where D2 points to is the previous job

Multiple search and return multiple values for same matches

Sub MultipleSearch()
Dim aNum(), aTabl(), aRes()
Dim i As Long, k As Long, n As Long
With Worksheets("List1")
i = .Cells(.Rows.Count, "A").End(xlUp).Row
aTabl = .Range("A1:G" & i).Value
End With
With Worksheets("List2")
i = .Cells(.Rows.Count, "D").End(xlUp).Row
aNum = .Range("D1:D" & i).Value
ReDim aRes(1 To i + 3, 1 To 1)
aRes(1, 1) = .Range("K1").Value
End With
For i = 2 To UBound(aNum)
n = i
For k = 2 To UBound(aTabl)
If aTabl(k, 1) <> Empty Then
If aNum(i, 1) = aTabl(k, 1) Then
aRes(n, 1) = aTabl(k, 7): n = n + 1
End If
End If
Next k
Next i
Worksheets("List2").Range("K1").Resize(UBound(aRes), 1).Value = aRes
End Sub
Hi. I have a code that lookup value on Picture 1 in 4.column and value on Picture 2 in 1.column and when it matches it returns value from Picture 2. from 8.column as you see.
I need to make a change. I need to return value from 8.column in other way.
When its the first match of 26054112 i need to return the first number from 8.column - -10629425,25
When its second match of 26054112 i need to return the second number - -1549761,31
And so on...

Matching a number in a column with the same number in the other column using VBA Excel

First, I'd like to apologize for this poor question and I hope it doesn't upset anyone here. Since I'm not that good at speaking English to convey my request, so please have a look to the cited links in order to get a clear explanation to this question.
I'm trying to find the solution to this question of mine. I started my attempt by searching for the same number in column A and column B (Debit and Credit). I used the looping-trough-array method to do it instead of employing the Find function like this question since I think it's faster.
Suppose that I have the following set data in Sheet1 and start from row 1 column A:
D e b i t Cr e d i t
20 13
14 13
13 14
14 17
19 19
11 20
17 14
20 12
19 19
20 15
20 12
13 11
12 19
13 20
19 19
20 11
11 16
10 16
19 19
20 11
Now, I'd like to process the data set above to something like this:
Basically, I need to find the same value of debit and credit in a specific row and match it with debit and credit in another row. Column C (Row) indicates the matched values. For example, the debit value in row 2 match with the credit value in row 15 and vice-versa. And numbers in column D (ID Match) are the label numbers to indicate the order of the matched data that's found first. This is my code in an attempt to implement the task:
Public i As Long, j As Long, k As Long, Last_Row As Long
Public DC, Row_Data, ID_Match
Sub Quick_Match()
T0 = Timer
k = 0
Last_Row = Cells(Rows.Count, "A").End(xlUp).Row
ReDim DC(1 To Last_Row - 1, 1 To 2)
ReDim Row_Data(1 To Last_Row - 1, 1 To 1)
ReDim ID_Match(1 To Last_Row - 1, 1 To 1)
DC = Range("A2:B" & Last_Row)
For i = 1 To Last_Row - 1
If DC(i, 1) <> "" Then
k = k + 1
For j = 1 To Last_Row - 1
If DC(i, 1) <> DC(i, 2) Then
If DC(i, 1) = DC(j, 2) And DC(i, 2) = DC(j, 1) Then
Call Row_Label
Exit For
Else
Row_Data(i, 1) = "No Match"
End If
Else
If i <> j Then
If DC(i, 1) = DC(j, 1) And DC(i, 2) = DC(j, 2) Then
Call Row_Label
Exit For
Else
Row_Data(i, 1) = "No Match"
End If
End If
End If
Next j
End If
If Row_Data(i, 1) = "No Match" Then
k = k - 1
End If
Next i
Range("C2:C" & Last_Row) = Row_Data
Range("D2:D" & Last_Row) = ID_Match
InputBox "The runtime of this program is ", "Runtime", Timer - T0
End Sub
Sub Row_Label()
Row_Data(i, 1) = j + 1
ID_Match(i, 1) = k
Row_Data(j, 1) = i + 1
ID_Match(j, 1) = k
DC(i, 1) = ""
DC(i, 2) = ""
DC(j, 1) = ""
DC(j, 2) = ""
End Sub
Though it's a bit slow on its performance, but it works fine. It completes in about 25 seconds on my machine for processing 10,000 rows of data (the data set file can be downloaded on this link for testing the running time of your code and mine). So I'm wondering if there is a more effective way for doing this. Could anyone come up with either a shorter version or a quicker version? Please do share your attempt.
Our ID's don't are different because I don't search ahead in the list for a match. I iterate over the list one time adding keys to a dictionary. If a find a key already exists that matches your criteria assign the new ID and row numbers.
Let me know if this meets your criteria.
Sub DebitCreditCrossMatch()
Dim dictKeys As Object, dictRows As Object
Dim DebitKey As String, CreditKey As String
Dim arrDebit, arrCredit, arrMatchRow, arrMatchID, items, keys
Dim ID As Long, rw As Long, x As Long, lastRow As Long
lastRow = Cells(Rows.count, "A").End(xlUp).Row
arrDebit = Range("A1", "A" & lastRow).Value
arrCredit = Range("B1", "B" & lastRow).Value
arrMatchRow = Range("C1", "C" & lastRow).Value
arrMatchID = Range("D1", "D" & lastRow).Value
Set dictKeys = CreateObject("Scripting.Dictionary")
For x = 2 To lastRow
arrMatchRow(x, 1) = "No Match"
arrMatchID(x, 1) = "No Match"
DebitKey = arrDebit(x, 1) & ":" & arrCredit(x, 1)
CreditKey = arrCredit(x, 1) & ":" & arrDebit(x, 1)
If dictKeys.Exists(CreditKey) Then
Set dictRows = dictKeys(CreditKey)
items = dictRows.items
keys = dictRows.keys
rw = CLng(items(0))
ID = ID + 1
arrMatchRow(x, 1) = rw
arrMatchRow(rw, 1) = x
arrMatchID(x, 1) = ID
arrMatchID(rw, 1) = ID
dictRows.Remove keys(0)
If dictRows.count = 0 Then dictKeys.Remove CreditKey
ElseIf dictKeys.Exists(DebitKey) Then
Set dictRows = dictKeys(DebitKey)
dictRows.Add x, x
Else
Set dictRows = CreateObject("Scripting.Dictionary")
dictRows.Add x, x
dictKeys.Add DebitKey, dictRows
End If
Next
Range("C1", "C" & lastRow).Value = arrMatchRow
Range("D1", "D" & lastRow).Value = arrMatchID
Set dictKeys = Nothing
Set dictRows = Nothing
End Sub
I reworked my previous answer introducing a second loop; so that our ID numbers will match.
Sub DebitCreditCrossMatch()
Dim dictKeys As Object, dictRows As Object
Dim DebitKey As String, CreditKey As String
Dim arrDebit, arrCredit, items, keys
Dim arrMatchRow(), arrMatchID()
Dim ID As Long, rw As Long, x As Long, lastRow As Long
lastRow = Cells(Rows.count, "A").End(xlUp).Row
arrDebit = Range("A1", "A" & lastRow).Value
arrCredit = Range("B1", "B" & lastRow).Value
ReDim arrMatchID(lastRow - 2)
ReDim arrMatchRow(lastRow - 2)
Set dictKeys = CreateObject("Scripting.Dictionary")
For x = 2 To lastRow
DebitKey = arrDebit(x, 1) & ":" & arrCredit(x, 1)
CreditKey = arrCredit(x, 1) & ":" & arrDebit(x, 1)
If dictKeys.Exists(CreditKey) Then
Set dictRows = dictKeys(CreditKey)
items = dictRows.items
keys = dictRows.keys
rw = CLng(items(0))
arrMatchRow(x - 2) = rw
arrMatchRow(rw - 2) = x
dictRows.Remove keys(0)
If dictRows.count = 0 Then dictKeys.Remove CreditKey
ElseIf dictKeys.Exists(DebitKey) Then
Set dictRows = dictKeys(DebitKey)
dictRows.Add x, x
Else
Set dictRows = CreateObject("Scripting.Dictionary")
dictRows.Add x, x
dictKeys.Add DebitKey, dictRows
End If
Next
For x = 0 To lastRow - 2
If Not IsEmpty(arrMatchRow(x)) And IsEmpty(arrMatchID(x)) Then
rw = arrMatchRow(x) - 2
arrMatchRow(rw) = x + 2
ID = ID + 1
arrMatchID(x) = ID
arrMatchID(rw) = ID
Else
If IsEmpty(arrMatchRow(x)) Then
arrMatchRow(x) = "No Match"
End If
End If
Next
Range("C2", "C" & lastRow).Value = WorksheetFunction.Transpose(arrMatchRow)
Range("D2", "D" & lastRow).Value = WorksheetFunction.Transpose(arrMatchID)
Set dictKeys = Nothing
Set dictRows = Nothing
End Sub
Assuming there are no duplicated in the Credit-Debit pairs you could use the following methods in a separate module calling matchCreditDebit() and adjusting the ranges in the initialization phase as needed:
Option Explicit
Public Sub matchCreditDebit()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim debit As Range, credit As Range, match As Range, rows As Long
rows = ws.UsedRange.rows.Count
Set credit = ws.Range("A1:A" & rows)
Set debit = ws.Range("B1:B" & rows)
Set match = ws.Range("C1:C" & rows)
match.Offset(1).Clear 'delete previous matched, start with clean slate, offset used to preserve header
Dim foundRanges As Collection, i As Long, r As Range
For i = 2 To rows
If Len(ws.Cells(i, match.Column).Value) = 0 _
And Len(ws.Cells(i, credit.Column).Value) > 0 Then 'check if match is already found and credit has value
Set foundRanges = FindAllInRange(debit, credit.Cells(i, 1).Value) 'first sift, find matching debit with a credit value
If Not foundRanges Is Nothing Then
For Each r In foundRanges
Debug.Print r.Address, ws.Cells(r.Row, credit.Column).Value
If ws.Cells(r.Row, credit.Column).Value = ws.Cells(i, debit.Column) Then 'second sift, match for found credit in debit
ws.Cells(r.Row, match.Column).Value = i
End If
Next r
End If
End If
Next i
End Sub
Public Function FindAllInRange( _
ByRef searchRange As Range, _
ByVal FindWhat As Variant _
) As Collection
Dim result As Collection
Set result = New Collection
Dim nextFound As Range
Set nextFound = searchRange.Cells(searchRange.rows.Count, 1)
Do
Set nextFound = searchRange.Find( _
What:=FindWhat, _
After:=nextFound, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows)
If nextFound Is Nothing Then Exit Do
If collectionContainsRange(result, nextFound) Then Exit Do
result.Add nextFound, nextFound.Address
Loop While True
Set FindAllInRange = result
End Function
Private Function collectionContainsRange(ByRef result As Collection, ByRef rng As Range) As Boolean
collectionContainsRange = False
Dim r As Range
For Each r In result
If StrComp(r.Address, rng.Address, vbTextCompare) = 0 Then
collectionContainsRange = True
Exit Function
End If
Next r
End Function
Generally what this does is find the ranges that match the first criteriaon and put them into a collection and from those sift out the ones that match the second criteria. However if there are duplicate pairs the last found reverse pair will be entered as ref.
Edit comments: Starting inner loop without lctrRow will not do back-checking. Reinstated original code.
Sub test()
'/ Assuming that on Sheet1 starting at A1, four headers are : Debit Credit Row ID Match
Dim lCtrRow As Long
Dim lCtrRow2 As Long
Dim lmatchCount As Long
Dim arrResult
arrResult = Sheet1.UsedRange
'/ Loop through first column Rows
For lCtrRow = LBound(arrResult) To UBound(arrResult)
lmatchCount = 0
arrResult(lCtrRow, 3) = "No Match"
'/ Re-Loop but this time match if A&B = B&A
For lCtrRow2 = LBound(arrResult) + 1 To UBound(arrResult)
If arrResult(lCtrRow, 1) & arrResult(lCtrRow, 2) = arrResult(lCtrRow2, 2) & arrResult(lCtrRow2, 1) Then
'/ If no match then only put down the row number. Avoids overwriting.
If arrResult(lCtrRow, 3) = "No Match" Then
arrResult(lCtrRow, 3) = lCtrRow2
End If
'/ Keep track of no. matches found.
lmatchCount = lmatchCount + 1
arrResult(lCtrRow, 4) = lmatchCount
End If
Next
Next
'/ Dump the processed result back on another sheet
Sheet2.Range("a1").Resize(UBound(arrResult), UBound(arrResult, 2)) = arrResult
End Sub
This worked for me:
Sub Matching()
Dim rng, arr, r1 As Long, r2 As Long, nR As Long
Dim sortId As Long, rwTrack(), s1, s2
'get the input range
With Range("a1").CurrentRegion 'assumes no blank columns/rows
Set rng = .Offset(1, 0).Resize(.Rows.Count - 1)
End With
arr = rng.Value
nR = UBound(arr, 1)
ReDim rwTrack(1 To nR) 'for matching row numbers to sortId
' (should be more like nR/2 but lazy...)
sortId = 1
For r1 = 1 To nR
For r2 = r1 + 1 To nR
If arr(r1, 1) = arr(r2, 2) And arr(r1, 2) = arr(r2, 1) Then
s1 = arr(r1, 4)
s2 = arr(r2, 4)
If Len(s1) = 0 And Len(s2) = 0 Then
'new match - assign new Id
arr(r1, 4) = sortId
arr(r2, 4) = sortId
rwTrack(sortId) = r1 & "," & r2 'capture the rows
sortId = sortId + 1
Else
'already matched: copy the existing Id and track rows
If Len(s1) > 0 And Len(s2) = 0 Then
arr(r2, 4) = s1
rwTrack(s1) = rwTrack(s1) & "," & r2
End If
If Len(s2) > 0 And Len(s1) = 0 Then
arr(r1, 4) = s2
rwTrack(s2) = rwTrack(s2) & "," & r1
End If
End If
End If
Next r2
Next r1
'populate all of the matched row numbers
For r1 = 1 To nR
If arr(r1, 4) <> "" Then arr(r1, 3) = rwTrack(arr(r1, 4))
Next r1
'dump the data back
Range("a1").Offset(1, 0).Resize(nR, UBound(arr, 2)).Value = arr
End Sub
Before and after:
To speed up the matching, can improve thru Algorithm.
Assuming your code are working fine.
1) We can sort Column A then Column B, therefore, your data will be like this
Row A B
2 20 13
3 20 12
4 20 11
.
.
.
998 13 20
999 12 20
1000 11 20
.
.
.
2) While looping Debit column to find the first value 20 within Credit column might have a very huge gap. We can then add in application.Match(20,Range("B:B"),0) to find out the row to start the loop.
Base on above assumption, we can reduce about 1000 times of loop. (in real case, it could be more/less). Application.Match() is lot more faster than looping one by one.
3) Exit the loop, when Credit value is less than Debit value, because we have sort the data in sequence, we can assume there have no possible match, when Credit < Debit.
4) While the use Application.ScreenUpdating = False, can increase the processing speed.
5) Without touch the original data, also can use Application.Match to reduce row by row loop.
Assume, you have 10K records,
first set searchRng as C1:C10000, then match to find the row of the first Debit value (20, base on yr photo),
then we found matched record on Row 7, check if the record match both Debit & Credit, if not reduce the resize of searchRng to C8:C10000 then keep repeating the logic
Sub Match ()
For nRow = 2 to lastRow 'Loop for each row in Column A
set searchRng = Range("C1:C10000")
debitVal = Cells(nRow, "B")
Do until searchRng is Nothing
If IsError(Application.Match(debitVal, searchRng, 0)) then
'No Match
Exit Do
Else
N = Application.Match(debitVal, searchRng, 0)
'Do something to check if Record match, and assign ID Match
If IsRecordMatched Then
'Assign ID
'Matching Range - Cells(nRow,"B")
'Matched Range - Cells(searchRng.Cells(1).Offset(N,0).Rows, "B")
Else
'Resize the searchRng
nSize = searchRng.Cells.Count - (N + 1)
if nSize < 1 then Exit Do
set searchRng = searchRng.Resize(nSize,1)
set searchRng = searchRng.Offset(N + 1,0)
End If
End If
Loop
Next nRow
End Sub
Above code not tested. Please take it as reference.
Improvement
The following code completes less than 2.4 seconds on average. It's twice faster than the previous one and also shorter.
Sub Quick_Match()
Dim i As Long, j As Long, k As Long, Last_Row As Long
Dim DC, Row_Data, ID_Match
Last_Row = Cells(Rows.Count, "A").End(xlUp).Row
ReDim DC(1 To Last_Row - 1, 1 To 2)
ReDim Row_Data(1 To Last_Row - 1, 1 To 1)
ReDim ID_Match(1 To Last_Row - 1, 1 To 1)
DC = Range("A2:B" & Last_Row).Value2
For i = 1 To Last_Row - 2
If DC(i, 1) <> vbNullString Then
k = k + 1
For j = i + 1 To Last_Row - 1
If DC(j, 2) <> vbNullString Then
If DC(i, 1) = DC(j, 2) And DC(i, 2) = DC(j, 1) Then
Row_Data(i, 1) = j + 1: ID_Match(i, 1) = k
Row_Data(j, 1) = i + 1: ID_Match(j, 1) = k
DC(i, 1) = vbNullString: DC(i, 2) = vbNullString
DC(j, 1) = vbNullString: DC(j, 2) = vbNullString
Exit For
End If
End If
Next j
End If
If Row_Data(i, 1) = vbNullString Then
Row_Data(i, 1) = "No Match": k = k - 1
End If
Next i
Range("C2:C" & Last_Row) = Row_Data
Range("D2:D" & Last_Row) = ID_Match
End Sub
[Old answer] Some progress. The following code completes less than 5.2 seconds on average for processing 10,000 rows of data on my machine. Not only is faster, but it's also a bit shorter than the previous one. I changed the looping algorithm to improve its performance. I also use some speedup tricks like using .Value2rather than the default property (.Value) makes Excel do less processing and assigning vbNullString instead of Zero Length String ("") to the elements of the array that has found its match or labeled "No Match" so that the loop procedure won't process it again.
Public i As Long, j As Long, k As Long, Last_Row As Long
Public DC, Row_Data, ID_Match
Sub Quick_Match()
T0 = Timer
k = 0
Last_Row = Cells(Rows.Count, "A").End(xlUp).Row
ReDim DC(1 To Last_Row - 1, 1 To 2)
ReDim Row_Data(1 To Last_Row - 1, 1 To 1)
ReDim ID_Match(1 To Last_Row - 1, 1 To 1)
DC = Range("A2:B" & Last_Row).Value2
For i = 1 To Last_Row - 1
If DC(i, 1) <> vbNullString Then
k = k + 1
For j = 1 To Last_Row - 1
If DC(j, 2) <> vbNullString Then
If DC(i, 1) <> DC(i, 2) Then
If DC(i, 1) = DC(j, 2) And DC(i, 2) = DC(j, 1) Then
Call Row_Label
Exit For
End If
Else
If i <> j Then
If DC(i, 1) = DC(j, 1) And DC(i, 2) = DC(j, 2) Then
Call Row_Label
Exit For
End If
End If
End If
End If
Next j
End If
If Row_Data(i, 1) = vbNullString Then
Row_Data(i, 1) = "No Match"
DC(i, 2) = vbNullString
k = k - 1
End If
Next i
Range("C2:C" & Last_Row) = Row_Data
Range("D2:D" & Last_Row) = ID_Match
InputBox "The runtime of this program is ", "Runtime", Timer - T0
End Sub
Sub Row_Label()
Row_Data(i, 1) = j + 1
ID_Match(i, 1) = k
Row_Data(j, 1) = i + 1
ID_Match(j, 1) = k
DC(i, 2) = vbNullString
DC(j, 1) = vbNullString
DC(j, 2) = vbNullString
End Sub

Excel Loop through list,transpose and create a matrix based on cell content

I am receiving a large file 500k+ lines but all the content is in column A. I need to run a macro that will transpose the data into matrix form but will only create a new row when it finds "KEY*" in the ActiveCell. For example:
| KEY 4759839 | asljhk | 35049 | | sklahksdjf|
| KEY 359 | skj | 487 |y| 2985789 |
The above data in my file would originally look like this in column A:
KEY 4759839
asljhk
35049
sklahksdjf
KEY 359
skj
487
y
2985789
Considerations:
Blank cells need to be transposed as well, so the macro cant stop based on emptyCell
The number of cells between KEY's is not constant so it actually needs to read the cell to know if it should create a new row
It can either stop based on say 20 empty cells in a row or prompt for a max row number
(Optional) It would be nice if there was some sort of visual indicator for the last item in a row so that its possible to tell if the last item(s) were blank cells
I searched around and found a macro that had the same general theme but it went based on every 6 lines and I did not know enough to try to modify it for my case. But in case it helps here it is:
Sub kTest()
Dim a, w(), i As Long, j As Long, c As Integer
a = Range([a1], [a500000].End(xlUp))
ReDim w(1 To UBound(a, 1), 1 To 6)
j = 1
For i = 1 To UBound(a, 1)
c = 1 + (i - 1) Mod 6: w(j, c) = a(i, 1)
If c = 6 Then j = j + 1
Next i
[c1].Resize(j, 6) = w
End Sub
I would greatly appreciate any help you can give me!
This works with the sample data you provided in your question - it outputs the result in a table starting in B1. It runs in less than one second for 500k rows on my machine.
Sub kTest()
Dim originalData As Variant
Dim result As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim countKeys As Long
Dim countColumns As Long
Dim maxColumns As Long
originalData = Range([a1], [a500000].End(xlUp))
countKeys = 0
maxColumns = 0
'Calculate the number of lines and columns that will be required
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
countKeys = countKeys + 1
maxColumns = IIf(countColumns > maxColumns, countColumns, maxColumns)
countColumns = 1
Else
countColumns = countColumns + 1
End If
Next i
'Create the resulting array
ReDim result(1 To countKeys, 1 To maxColumns) As Variant
j = 0
k = 1
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
j = j + 1
k = 1
Else
k = k + 1
End If
result(j, k) = originalData(i, 1)
Next i
With ActiveSheet
.Cells(1, 2).Resize(UBound(result, 1), UBound(result, 2)) = result
End With
End Sub
Tested and works:
Sub test()
Row = 0
col = 1
'Find the last not empty cell by selecting the bottom cell and moving up
Max = Range("A650000").End(xlUp).Row 'Or whatever the last allowed row number is
'loop through the data
For i = 1 To Max
'Check if the left 3 characters of the cell are "KEY" and start a new row if they are
If (Left(Range("A" & i).Value, 3) = "KEY") Then
Row = Row + 1
col = 1
End If
Cells(Row, col).Value = Range("A" & i).Value
If (i > Row) Then
Range("A" & i).Value = ""
End If
col = col + 1
Next i
End Sub