Sub xLator2()
Dim s1 As Worksheet, s2 As Worksheet
Dim N As Long, i As Long
Dim from(), too()
Set s1 = Sheets("Sheet1") ' contains the data
Set s2 = Sheets("Sheet2") ' contains the translation table
s2.Activate
N = Cells(Rows.Count, 1).End(xlUp).Row
ReDim from(1 To N)
ReDim too(1 To N)
For i = 1 To N
from(i) = Cells(i, 1).Value
too(i) = Cells(i, 2).Value
Next i
s1.Activate
' -------------- Modification starts here --------------------------
' Replace from from(i) to __MYREPLACEMENTi__ (where i is the counter)
For i = LBound(from) To UBound(from)
Cells.Replace What:=from(i), Replacement:="__MYREPLACEMENT" + Str(i) + "__"
Next i
' Replace from __MYREPLACEMENTi__ to too(i) (where i is the counter)
For i = LBound(from) To UBound(from)
Cells.Replace What:="__MYREPLACEMENT" + Str(i) + "__", Replacement:=too(i)
Next i
' -------------- Modification ends here --------------------------
End Sub
I am using above code to find and replace multiple words (in "Column A Sheet1" with words in "Column B Sheet 2") in below mentioned sheet:
https://docs.google.com/spreadsheets/d/14ba9pQDjMPWJd4YFpGffhtVcHxml0LdUUVQ0prrOEUY/edit?usp=sharing
However when I apply this in another sheet (as mentioned below) for another data then code fails i.e. I get distorted words in sheet1:
https://docs.google.com/spreadsheets/d/1spvZAzxT1kB1bytCQaNQH7tl1DJSpLITYgW6P5dxbQE/edit?usp=sharing
Please help me out so that I can replace words in "Column A Sheet1" with words in "Column B Sheet2"
Note: Above links have been given of google spreadsheet however I am having problem in Excel 2007 sheet.
I request you to help me by giving whole revised code as I am not good in VBA
I agree with sous2817: the more you solve your own problems, the faster you will develop. However, I think a little advice will help.
You must learn VBA if you are going to use it. Finding, and attempting to use, a piece of code which you do not understand is never going to end well. This is particularly true when the author of this code only knows a little more than you.
Search the web for "Excel VBA Tutorial". There are many to choose from so try a few and complete one that matches your learning style. I prefer books. I visited a large library, reviewed their Excel VBA Primers and borrowed those I liked. After trying them out at home, I bought the one that suited me best.
Where did you get this code? It contains typical beginner mistakes and it does not work even with the first example.
The first word in Sheet1 is "it". Sheet2 indicates "it" is to be replaced by "that". The code correctly replaces "it" by "that" Unfortunately, it replaces all "it"s by "that"s so "with" is translated to "wthath" not "having". Since you complain about the second pair of sheets, I assume you did not notice that mistranslation. Such mistranslations will be must more obvious in the second pair with the first word "the" appearing in "they", "there", "their", "them", "then" and "these".
If you look up the Replace Method, you should see a quick correction to this error. Note: Replace Method not Replace Function.
I will end by giving a couple examples of how a less junior programmer might have coded this routine.
Consider:
s2.Activate
N = Cells(Rows.Count, 1).End(xlUp).Row
ReDim from(1 To N)
ReDim too(1 To N)
For i = 1 To N
from(i) = Cells(i, 1).Value
too(i) = Cells(i, 2).Value
Next i
One of the first things you learn when you start learning VBA is “Do not activate worksheets or select cells”. These are slow commands and even if you use ScreenUpdating = False there will be some screen rewriting. More importantly, your code can become very confusing. The following is better:
With s2
N = .Cells(Rows.Count, 1).End(xlUp).Row
ReDim from(1 To N)
ReDim too(1 To N)
For i = 1 To N
from(i) = .Cells(i, 1).Value
too(i) = .Cells(i, 2).Value
Next i
End With
Note 1: the periods before the three Cells. Cells operates on the active worksheet. .Cells operates on the worksheet specified in the With statement.
Note 2: I have not placed a period before Rows.Count. Rows.Count returns the number of rows in the active worksheet. .Rows.Count returns the number of rows in the specified worksheet. The number of rows depends on the version of Excel and does not vary from worksheet to worksheet so most programmers do not bother with the period.
I would have made the two Worksheets parameters:
Sub xLator2(s1 As Worksheet, s2 As Worksheet)
This makes the subroutine much more flexible. I would probably go further and make the parameters Ranges which would give even more flexibility.
I could continue but I believe that is enough to start with. Good luck and welcome to the joys of programming.
Edit: Tutorial and complete solution
Part 1 - One reason not use Activate
Please study the following blocks of code which show why only the most junior VBA programmers use Activate without a very good reason. I do not want you to get too uptight about saving a few seconds or milliseconds. There are programmers who will spend half-an-hour to optimise a routine that is only run now and again. Only if a routine is to be run hundreds of times a day can that time be justified. I want you to appreciate that Application.ScreenUpdating = False can save a lot of time so you use it automatically. When Application.ScreenUpdating = False is included, the difference between Activate and With is a lot less but enough to justify making With the default choice.
For Count = 1 To 10000 ' This takes 148 seconds
Worksheets("Sheet1").Activate
Worksheets("Sheet2").Activate
Next
Application.ScreenUpdating = False ' This takes 11 seconds
For Count = 1 To 10000
Worksheets("Sheet1").Activate
Worksheets("Sheet2").Activate
Next
Application.ScreenUpdating = False ' This takes .07 seconds
For Count = 1 To 10000
With Worksheets("Sheet1")
End With
With Worksheets("Sheet2")
End With
Next
Application.ScreenUpdating = False ' This takes 12 seconds
For Count = 1 To 10000
Worksheets("Sheet1").Activate
Cells(23, 1).Value = "A"
Worksheets("Sheet2").Activate
Cells(23, 1).Value = "A"
Next
Application.ScreenUpdating = False ' This takes 1.16 seconds
For Count = 1 To 10000
With Worksheets("Sheet1")
.Cells(23, 1).Value = "A"
End With
With Worksheets("Sheet2")
.Cells(23, 1).Value = "A"
End With
Next
Application.ScreenUpdating = False ' This takes 0.96 seconds
Set Wsht1 = Worksheets("Sheet1")
Set Wsht2 = Worksheets("Sheet2")
For Count = 1 To 10000
With Wsht1
.Cells(23, 1).Value = "A"
End With
With Wsht2
.Cells(23, 1).Value = "A"
End With
Next
Part 2 - Copying values from a worksheet to an array
Macro LoadFromTo1() is based on the opening code of your macro which loads the From and Too tables. It is slightly different because my test data is slightly different. It takes six-tenths of a seconds to load the From and Too table
Sub LoadFromTo1()
' Takes about .594 seconds for 50,000 rows * 2 columns
Dim s1 As Worksheet
Dim N As Long, i As Long
Dim From(), too()
Dim InxFromTo As Long
Dim TimeStart As Single
TimeStart = Timer
Set s1 = Sheets("Test1") ' contains the data
s1.Activate
N = Cells(Rows.Count, 3).End(xlUp).Row
ReDim From(1 To N - 1)
ReDim too(1 To N - 1)
For i = 2 To N
From(i - 1) = Cells(i, 3).Value
too(i - 1) = Cells(i, 4).Value
Next i
Debug.Print "M1: " & Timer - TimeStart
For InxFromTo = 1 To 20
Debug.Print Right(" " & InxFromTo, 5) & " " & From(InxFromTo) & " " & too(InxFromTo)
Next
For InxFromTo = UBound(From) - 20 To UBound(From)
Debug.Print Right(" " & InxFromTo, 5) & " " & From(InxFromTo) & " " & too(InxFromTo)
Next
End Sub
Macro LoadFromTo2() (not shown) used With instead of Activate. With only only one Activate or With there was no noticiable change in performance.
Macro LoadFromTo3() uses a different technique for loading the tables. Insteading of importing the cell values one at a time, it imports then in a single statement: CellValue = .Range(.Cells(2, 3), .Cells(RowMax, 4)).Value. This statement will probably seems strange to you now. However, if you study and practice the technique, it will become second nature. I find LoadFromTo3() easier to code and understand than LoadFromTo1() and it is ten times as fast. I have read that importing a range as a unit rather than cell by cell can be fifty times faster although I have never achieved that level of performance improvement.
Sub LoadFromTo3()
' Takes about .0625 seconds for 50,000 rows * 2 columns
Const ColFrom As Long = 1
Const ColTo As Long = 2
Dim s1 As Worksheet
Dim RowMax As Long, RowCrnt As Long
Dim InxFromTo As Long
Dim TimeStart As Single
Dim CellValue As Variant
TimeStart = Timer
Set s1 = Sheets("Test1") ' contains the data
With s1
RowMax = .Cells(Rows.Count, 3).End(xlUp).Row
CellValue = .Range(.Cells(2, 3), .Cells(RowMax, 4)).Value
Debug.Print "M3: " & Timer - TimeStart
End With
For InxFromTo = 1 To 20
Debug.Print Right(" " & InxFromTo, 5) & " " & CellValue(InxFromTo, ColFrom) & _
" " & CellValue(InxFromTo, ColTo)
Next
For InxFromTo = UBound(CellValue, 1) - 20 To UBound(CellValue, 1)
Debug.Print Right(" " & InxFromTo, 5) & " " & CellValue(InxFromTo, ColFrom) & _
" " & CellValue(InxFromTo, ColTo)
Next
End Sub
Part 3 - Analysis of original code
First some definitions. The Target range is the column of words to be translated. The Target table is the the Target range loaded into memory. The FromTo Range is the From and To columns. The FromTo table is the the FromTo range loaded into memory.
This section has taken longer than I originally planned. Initially I only intended to conduct a proper test of my code. With the error in the code provided in an earlier answer and the implication there was another error, I was especially careful. I coded a routine that generated test data of varying sizes. I used that routine to generate the 50,000 row FromTo table I used for the timings in Part 2. I made some minor changes to the translate routine you published and ran it against my test data to give the following durations:
FromTo Target Target Duration
Rows Rows Cols in secs
50,000 20 1 103
50,000 10,000 1 486
10,000 3,000 2 60
The key section of my modified code was:
With RngTgt
For RowFromTo = 1 To UBound(FromToTable, 1)
.Replace What:=FromToTable(RowFromTo, ColFrom), _
Replacement:="__" + Str(RowFromTo), _
LookAt:=xlWhole
Next
For RowFromTo = 1 To UBound(FromToTable, 1)
.Replace What:="__" + Str(RowFromTo), _
Replacement:=FromToTable(RowFromTo, ColTo), _
LookAt:=xlWhole
Next
End With
I use a range so the Target range can be in any worksheet and be multi-column. I did not see the point of having such a large prefix and suffix which must result in a time penalty so I reduced to a prefix of two underscores. I include the LookAt:=xlWhole correction. I use a ToFrom table loaded from range. I have replaced i by the meaningful name RowFromTo to make the code more understandable.
There are no comments saying what this code does and why it does it this way. It is essential to include adequate comments in a macro. Will you remember what this macro does in six or twelve months? What if a colleague needs to amend it?
In your first FromTo table, "for" translates to "on" and "on" translates" to "upon". Without the two pass solution. "for" might be translated to "upon". This is my guess and it seems reasonable but there should have been a comment so I did not need to guess. A new programmer taking responsibility for this macro might be unable to guess and might "improve" the code by removing the "unnecessary" second pass.
The code behind the Replace method will be as efficient as its authors can manage but it will still have to examine every cell in the range. I have improved the situation by searching a specified range rather than the whole worksheet. However, it still has to search that range twice for every row in the FromTo table.
Studying this code I saw an immediate improvement. The first pass replaces the words in the Target table with "__1", "__2", "__n" and so on where 1, 2 and n are indices into the FromTo table. The second pass searches for "__1", "__2" and "__n". A better technique is to extract the 1, 2 and n and use them to access the correct entry in the FromTo table. With this better technique the duration were:
First Second
FromTo Target Target duration duration
Rows Rows Cols in secs in secs
50,000 20 1 103 52
50,000 10,000 1 486 257
10,000 3,000 2 60 32
That is, I halved the duration by changing the code to:
With RngTgt
For RowFromTo = 1 To UBound(ToFromTable, 1)
.Replace What:=ToFromTable(RowFromTo, ColFrom), _
Replacement:="__" + Str(RowFromTo), _
LookAt:=xlWhole
Next
End With
For Each Cell In RngTgt
Test = Mid(Cell.Value, 3)
If IsNumeric(Test) Then
Cell.Value = ToFromTable(Val(Test), ColTo)
End If
Next
However, I believed the basic approach to be wrong. If there are FT entries in the FromTo range and T entries in the Target range then:
With approach 1 there are 2 * FT replaces that require the searching of T cells.
With approach 2 there are FT replaces that require the searching of T cells and then T replaces.
Since the same word may repeat within the Target range, the search of the Target range must examine every cell. But what if we searched the From column of the FromTo rangw for each entry in the Target range? Unless there are a large number of words in the Target range that are missing from the FromTo range, an average FT / 2 entries will be examined before a match is found. Also there is no need for a second pass. We would expect a third approach based on this logical, to half the approach 2 durations.
I recoded the main part of my routine and repeated the tests
First Second Third
FromTo Target Target duration duration duration
Rows Rows Cols in secs in secs in secs
50,000 20 1 103 52 .13
50,000 10,000 1 486 257 61.51
10,000 3,000 2 60 32 7.54
This as a much bigger reduction in duration than I was expecting. I have some guesses as to the reason but I have not investigated further. I believe the final durations are acceptable. I have one further idea but I do not think it is worth the time to imvestigate.
The major lesson of the above is: think about your implementation startegy before implementing it. To me technique 3 is obviously superior to techniques 1 and 2 and I would have started with that technique. Some time spent on initial design can repay itself hansomely.
Part 4 - Final solution
You posted two workbooks each with the Target range in Sheet1 and the FromTo range in Sheet2. I created one workbook with the data from the second workbook copied to Sheet3 and Sheet3.
I amended your macro to call my macro:
Option Explicit
Sub xLator2()
Dim RngTgt As Range
Dim RngFromTo As Range
Dim RowMax As Long
Dim TimeStart As Single
With Worksheets("Sheet1")
RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngTgt = .Range(.Cells(1, "A"), .Cells(RowMax, "A"))
End With
With Worksheets("Sheet2")
RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngFromTo = .Range(.Cells(1, "A"), .Cells(RowMax, "B"))
End With
TimeStart = Timer
Call Translate3(RngTgt, RngFromTo)
Debug.Print "Sheet1 technique 3 duration: " & Timer - TimeStart
With Worksheets("Sheet3")
RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngTgt = .Range(.Cells(1, "A"), .Cells(RowMax, "A"))
End With
With Worksheets("Sheet4")
RowMax = .Cells(Rows.Count, "A").End(xlUp).Row
Set RngFromTo = .Range(.Cells(1, "A"), .Cells(RowMax, "B"))
End With
TimeStart = Timer
Call Translate3(RngTgt, RngFromTo)
Debug.Print "Sheet3 technique 3 duration: " & Timer - TimeStart
End Sub
My macro for technique 3 takes two ranges as its parameters so there can be multiple Target and FromTo ranges which can be placed where ever convenient:
Sub Translate3(ByVal RngTgt As Range, ByVal RngFromTo As Range)
' RngTgt A rectangle containing words to be translated
' RngFromTo Two columns with the left column containing the original values
' for words and the right column containing the values to replace
' the original values.
' Constants numbering the From and To columns within RngFromTo. This makes the
' code easier to understand than if 1 and 2 had been used.
Const ColFrom As Long = 1
Const ColTo As Long = 2
Dim ColTgtCrnt As Long
Dim Test As String
Dim RngFindStart As Range
Dim RngFrom As Range
Dim RngTemp As Range
Dim RowFromTo As Long
Dim RowTgtCrnt As Long
Dim TgtTable As Variant
' Check FromTo range has two columns
If RngFromTo.Columns.Count <> 2 Then
Call MsgBox("ToFrom table must have two columns", vbOKOnly)
Exit Sub
End If
' Load Target range to array
TgtTable = RngTgt.Value
' Set RngFrom to the From column of RngFromTo
Set RngFrom = RngFromTo.Columns(ColFrom)
' Set RngFindStart to first cell of RngFrom
Set RngFindStart = RngFrom.Rows(1)
' Loop for every entry in Target table
For RowTgtCrnt = 1 To UBound(TgtTable, 1)
For ColTgtCrnt = 1 To UBound(TgtTable, 2)
Set RngTemp = RngFrom.Find(What:=TgtTable(RowTgtCrnt, ColTgtCrnt), _
After:=RngFindStart, _
LookAt:=xlWhole)
If Not RngTemp Is Nothing Then
' This target cell is to be translated
' Replace value in Target table with value for To column of FromTo table
TgtTable(RowTgtCrnt, ColTgtCrnt) = RngTemp.Offset(0, ColTo - ColFrom).Value
End If
Next
Next
' Upload updated array back to Target range
RngTgt.Value = TgtTable
End Sub
There is a lot to study here. Take it slowly and look up any statement you do not understand. Come back with questions if necessary but the more you understand on your own, the faster you will develop.
Related
I am trying to create a genera-table template in Excel. Eventually, I would like to be able to generate a template from a list of staff and tasks with any number of weeks in the rotation based on a Key.
So, when you hit generate if the key says one week then you would get each staff members name copied over into one row of the template sheet. However, if the key said 5 week, then it would copy each name 5 times in the Template sheet.
So, you would have a column with each staff's name listed 5 times and the next staff's name would be 5 times.
Below is the general shell that I have created in the hopes of achieving this. My question is two parts
Is this a reasonable method to achieve what I want
If this is a reasonable method then how would I actually tell it to copy the information from the staff sheet and paste it into the template sheet x number of times based on a Key cell which would say 1 week, 5 week, etc.
Sub Gernerate_Template()
For Each cell In Worksheets("Staff").Range("A:A")
If Worksheets("Template").Range("D1") = "1 Week" Then
Worksheets("Staff").Range("A1").Copy
Worksheets("Template").Range("A1").PasteSpecial
If Worksheets("Template").Range("D1") = "5 Week" Then
Worksheets("Staff").Range("A1").Copy
Worksheets("Template").Range("A1:A5").PasteSpecial
Exit For
Next cell
End Sub
You're on the right track, this should do what you want.
I'm assuming your value in D4 will always be "x Week"
I'm starting both sheets at row 1 since you don't appear to have a header row.
Sub populate_Template()
Dim i As Long
Dim y As Long
Dim count As Long
Dim repetition As Long
Dim lr As Long
Dim arr As Variant
With Sheets("Staff")
lr = .Cells(.Rows.count, 1).End(xlUp).Row
arr = .Range(.Cells(1, 1), .Cells(lr, 1))
repetition = Split(.Cells(1, 4).Value, " ")(0)
End With
Dim newarr() As String
ReDim newarr(1 To lr * repetition, 1 To 1)
With Sheets("Template")
count = 0
For i = 1 To lr
For y = 1 To repetition
count = count + 1
newarr(count, 1) = arr(i, 1)
Next y
Next i
.Range(.Cells(1, 1), .Cells(count, 1)).Value = newarr
End With
End Sub
Tested on 10,000 names repeated 10 times each takes about .6 seconds on my machine.
I have been given a work task where im to find and replace 8 digits numbers with a corresponding new values coming from a 2 column table....basically a vlookup then replace the old value with a new one...
The challenge im facing is.... the 2 column table is 882k rows, and the cells im trying to replace is about 120 million (41,000 rows x 3000 columns)...
I tried running a vba code i found somewhere...
Option Explicit
Sub Replace_Overwrite()
Dim LRow As Long, i As Long
Dim varSearch As Variant
With Sheets("Sheet2")
LRow = .Cells(.Rows.Count, 1).End(xlUp).Row
varSearch = .Range("A2:B" & LRow)
End With
With Sheets("Sheet1").UsedRange
For i = LBound(varSearch) To UBound(varSearch)
.Replace what:=varSearch(i, 1), replacement:=varSearch(i, 2), lookat:=xlWhole
Next
End With
End Sub
I tried using this and it ran it for 8 hours and my work laptop crashed....
I'm not sure anymore if this is still possible with MS Excel alone...
I wonder if anyone can help me with a code that can process it.. i can leave my system open over the weekend if its stable and does work.. it only has 8GB ram btw, running excel 2013...
To speed up things, do as much as possible in memory and minimize the interaction between VBA and Excel (as this makes things really slow).
The following attempt reads the lookup-list into a dictionary and then processes the data column by column.
I did a test, creating 880.000 lookup rows and 40.000 x 100 cells of data. Building the dictionary took less than a minute, processing the columns took 3-4 seconds per column. I added a logic that after every 10 columns, the whole workbook is saved, that increased the processing time but ensures that after a crash you can more or less continue where you left (the yellow color tells you where, just replace the 1 in for col=1 with the column where you want to restart).
I have added some DoEvents, that in theory slows down the process a little bit. Advantage is that you can see the output of the debug.print and the whole Excel process is not displayed as unresponsive in the task manager.
To build the dictionary, I read the complete data into an array at once (if you are not familiar with Dictionaries: You need to add a reference to the Microsoft Scripting Runtime).
Function createDict() As Dictionary
Dim d As New Dictionary
Dim rowCount As Long
Dim list()
Debug.Print Now, "Read data from Lookup sheet"
With ThisWorkbook.Sheets(1)
rowCount = .Cells(.Rows.Count, 1).End(xlUp).row
list = .Range("A1:B" & rowCount).Value
End With
Debug.Print Now, "Build dictionary."
Dim row As Long
For row = 1 To UBound(list)
If Not d.Exists(list(row, 1)) Then d.Add list(row, 1), list(row, 2)
If row Mod 1000 = 0 Then DoEvents
Next row
Set createDict = d
End Function
As said, replacing the data is done column by column. Again, I read the whole column at once into an array, do the replace on this array and then write it back to the sheet.
Sub replaceAll()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim d As Dictionary
Set d = createDict
Dim row As Long, col As Long
Dim rowCount As Long, colCount As Long
With ThisWorkbook.Sheets(2)
rowCount = .Cells(.Rows.Count, 1).End(xlUp).row
colCount = .Cells(1, .Columns.Count).End(xlToLeft).Column
For col = 1 To colCount
Debug.Print Now & "processing col " & col
DoEvents
Dim data
data = .Range(.Cells(1, col), .Cells(rowCount, col))
For row = 1 To rowCount
If d.Exists(data(row, 1)) Then data(row, 1) = d(data(row, 1))
Next row
.Range(.Cells(1, col), .Cells(rowCount, col)) = data
.Cells(1, col).Interior.Color = vbYellow
If col Mod 10 = 0 Then ThisWorkbook.Save
Next
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
One remark: You should consider to use a database for such amount of data.
I'm trying to make a unique ID for each sample in a variable length data set. to do this I want to use part of two strings of data called the Name and Sample Type. I want i to go down each row in the column and take the pieces of each string and put them together, however when I step through the loop it never goes into my loop, only around it. can someone tell me why?
Sheets("Data").Activate
setlastrow = Sheets("Data").Range("b5000").End(xlUp).Row
setlastcol = Sheets("Data").Cells(5, Columns.Count).End(xlToLeft).Column 'this is still assuming that row 5 has the header in it
colname = Rows(5).Find("Name", LookAt:=xlWhole).Column ' this can be repeated for any other columns we want to asign values to. These variables will make the rest of this much easier
colSampleText = Rows(5).Find("Sample Text", LookAt:=xlWhole).Column
For i = 6 To lastrow
Sheets("Data").Range(Cells(i, 1)) = workbookfunction.if(workbookfunction.CountIf(Range(Cells(6, colname), Cells(i, colname)), Cells(i, colname)) < 10, "0", "") & workbookfunction.CountIf(Range(Cells(6, colname), Cells(i, colname)), Cells(i, colname) & "-" & Left(Cells(i, colSampleText), 5))
'this should find the unique identifying infomation for each sample and analyte
Next i
There are two major errors in your code - plus a minor one. One is structural. You declare non of the variables you use. It's like saying, "Since I don't know how to drive I might as well close my eyes as we speed along". It's not without logic but does little toward getting you to where you want to go.
The other is in the mix-up between the worksheet function you want VBA to execute and the one you wish to assign to a cell to be executed by Excel. Writing a complex formula to a cell is more difficult than getting VBA to calculate a complex formula. For the method, if you want to create a formula in VBA you should assign it to a string first, like MyFormula = "=COUNTIF(D6:D12, "MyName")" and then, after testing it, assign that string to the cell's Formula property, like Cells(R, ClmName).Formula = MyFormula". In the code below I chose to let VBA do the calculating. Since it isn't entirely clear what you want (faulty code is never a good way to show what you intend!) please revise it. It's easier in VBA than in a worksheet function.
Private Sub Test()
Dim LastRow As Long
Dim LastClm As Long
Dim ClmName As Long ' R use "col" for color, "clm" for column
Dim ClmSampleText As Long
Dim CountRng As Range
Dim Output As Variant
Dim R As Long ' R use R for row, C for column
Sheets("Data").Activate
LastRow = Sheets("Data").Range("b5000").End(xlUp).Row
' this is still assuming that row 5 has the header in it
LastClm = Sheets("Data").Cells(5, Columns.Count).End(xlToLeft).Column
' this can be repeated for any other columns we want to asign values to.
' These variables will make the rest of this much easier
ClmName = Rows(5).Find("Name", LookAt:=xlWhole).Column
ClmSampleText = Rows(5).Find("Sample Text", LookAt:=xlWhole).Column
For R = 6 To LastRow
'this should find the unique identifying infomation for each sample and analyte
Set CountRng = Range(Cells(6, ClmName), Cells(R, ClmName))
Output = WorksheetFunction.CountIf(CountRng, Cells(R, ClmName).Value)
If Output < 10 Then Output = 0
Cells(R, 1).Value = CStr(Output) & "-" & Left(Cells(R, ClmSampleText).Value, 5)
Next R
End Sub
The "minor" mistake stems from your lack of understanding of the Cell object. A cell is a Range. It has many properties, like Cell.Row and Cell.Column or Cell.Address, and other properties like Cell.Value or Cell.Formula. The Value property is the default. Therefore Cell is the same as Cell.Value BUT not always. In this example, by not thinking of Cell.Value you also overlooked Cell.Formula, and by placing Cell into a WorksheetFunction you confused VBA as to what you meant, Cell the Value or Cell the Range. With all participants confused the outcome was predictable.
The recommendation is to always write Cell.Value when you mean the cell's value and use Cell alone only if you mean the range.
You have an error with the end part of your For...Next statement.
From the code you have posted, LastRow is not explicitly declared anywhere, so when you run your code, LastRow is created as Type Variant with a default Empty value.
Consider this code:
Sub LoopTest()
Dim DeclaredVariable As Long
Dim i As Long
DeclaredVariable = 10
For i = 1 To UnDeclaredVariable
Debug.Print i & " UnDeclaredVariable"
Next i
For i = 1 To DeclaredVariable
Debug.Print i & " DeclaredVariable"
Next i
End Sub
The output in the immidiate window would be:
1 DeclaredVariable
2 DeclaredVariable
3 DeclaredVariable
4 DeclaredVariable
5 DeclaredVariable
6 DeclaredVariable
7 DeclaredVariable
8 DeclaredVariable
9 DeclaredVariable
10 DeclaredVariable
This shows us that the loop for the UnDeclaredVariable has not been entered - AND this is due to the fact the end part of the For...Next loop is Empty (The default value of a Variant data type) so there is no defined end for the loop to iterate to.
NB To be more precise, the issue is that the UnDeclaredVariable has no (numeric) value assigned to it - if you assign a value to a variable that is undeclared it becomes a data type Variant/<Type of data you assigned to it> for example UnDeclaredVariable = 10 makes it a Variant/Intigertype .
The reason why it steps over the loop and doesn't throw an error is because you don't have Option Explicit at the top of your code module (or Tools > Options > "Require Variable Declaration" checked) which means the code can still run with undeclared variables (this includes if you spell a declared variable incorrectly).
If you add Option Explicit to the top of your code module:
Option Explicit
Sub LoopTest()
Dim DeclaredVariable As Long
Dim i As Long
DeclaredVariable = 10
For i = 1 To UnDeclaredVariable
Debug.Print i & " UnDeclaredVariable"
Next i
For i = 1 To DeclaredVariable
Debug.Print i & " DeclaredVariable"
Next i
End Sub
You would get the following error:
Compile Error:
Variable not defined
This is a fantastic example of why Option Explicit is an important declaration to make in all code modules.
Here is a variation of your code; I've modified your code to set your two columns using Find, loop through each cel in the range(using the current row), set varcnt to count the number of matches, defined the first 5 letters of value in the Sample Text column as str, and used a basic If statement to write the combined the unique ID into the first column.
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Data")
Dim lRow As Long: lRow = ws.Range("b5000").End(xlUp).Row
Dim dataCol As Long: dataCol = ws.Range("A5:J5").Find(What:="Name", LookIn:=xlValues, lookat:=xlWhole).Column
Dim smplTextCol As Long: smplTextCol = ws.Range("A5:J5").Find(What:="Sample Text", LookIn:=xlValues, lookat:=xlWhole).Column
For Each cel In ws.Range(ws.Cells(6, dataCol), ws.Cells(lRow, dataCol))
Dim varcnt As Long: varcnt = Application.WorksheetFunction.CountIf(ws.Range(ws.Cells(6, dataCol), ws.Cells(cel.Row, dataCol)), ws.Cells(cel.Row, dataCol).Value)
Dim str As String: str = Left(ws.Cells(cel.Row, smplTextCol).Value, 5)
If varcnt < "4" Then
ws.Cells(cel.Row, 1).Value = "0" & "-" & str
Else
ws.Cells(cel.Row, 1).Value = "" & "-" & str
End If
Next cel
I am attempting to run a VBA macro that iterates down about 67,000 rows with 100 columns in each row. For each of the cells in these rows, the value is compared against a column with 87 entries in another sheet. There are no errors noted when the code is run but Excel crashes every time. The odd thing is that the code seems to work; I have it set to mark each row in which a match is found and it does so before crashing. I have attempted to run it many times and it has gotten through between 800 and 11,000 rows before crashing, depending on the attempt.
My first suspect was memory overflow due to the volume of calculations but my system shows CPU utilization at 100% and memory usage around 50% while running this code:
Sub Verify()
Dim codes As String
Dim field As Object
For i = 2 To Sheets("DSaudit").Rows.Count
For Each field In Sheets("Dsaudit").Range(Cells(i, 12), Cells(i, 111))
r = 1
While r <= 87
codes = ThisWorkbook.Sheets("287 Denominator CPT").Cells(r, 1).Value
If field = codes Then
Cells(i, 112).Value = "True"
r = 88
Else
r = r + 1
End If
Wend
Next field
i = i + 1
Next i
End Sub
It should also be noted that I am still very new to VBA so it's likely I've made some sort of egregious rookie mistake. Can I make some alterations to this code to avoid a crash or should I scrap it and take a more efficient approach?
When ever possible iterate variant arrays. This limits the number of times vba needs to access the worksheet.
Every time the veil between vba and Excel is pierced cost time. This only pierces that veil 3 times not 9,031,385,088
Sub Verify()
With Sheets("DSaudit")
'Get last row of Data
Dim lastrow As Long
lastrow = .Cells(.Rows.Count, 12).End(xlUp).Row 'if column 12 ends before the last row of data change to column that has them all.
'Load Array with input Values
Dim rng As Variant
rng = .Range(.Cells(2, 12), .Cells(lastrow, 111)).Value
'Create output array
Dim outpt As Variant
ReDim outpt(1 To UBound(rng, 1), 1 To 1)
'Create Match array
Dim mtch As Variant
mtch = Worksheets("287 Denominator CPT").Range("A1:A87").Value
'Loop through first dimension(Row)
Dim i As Long
For i = LBound(rng, 1) To UBound(rng, 1)
'Loop second dimension(Column)
Dim j As Long
For j = LBound(rng, 2) To UBound(rng, 2)
'Loop Match array
Dim k As Long
For k = LBound(mtch, 1) To UBound(mtch, 1)
'If eqaul set value in output and exit the inner loop
If mtch(k, 1) = rng(i, j) Then
outpt(i, 1) = "True"
Exit For
End If
Next k
'If filled true then exit this for
If outpt(i, 1) = "True" Then Exit For
Next j
Next i
'Assign the values to the cells.
.Cells(2, 112).Resize(UBound(outpt, 1), 1).Value = outpt
End With
End Sub
I want make this basic function of "copy&paste-values-on-a-new-row-each-time" run as fast as possible since the macro repeats the calculations hundreds of thousands of times. I just can't find the exact answer after searching this forum for ages.
Currently, I'm copying output numbers from a fixed range and, elsewhere on the worksheet, pasting the values on a new row for each new set of results.
Here's the portion of the code doing this:
Row = Row +1
Range("g15:ax15").copy
Range("ea18").select
ActiveCell.Offset(Row,0).select
Selection.PasteSpecial Paste:=xlPasteValues
Now from what I have found on this forum, I can replace the Copy/Paste functions completely with Range(destination).value = Range(results).value to speed things up. However, I can't figure out how to do this if the destination rows need to be offset by 1 each time. Also, I've read that one could even do away with "select" to speed things up further! How?
There are a number of options:
//This uses the `Destination` key word
Sub CopyAndPaste()
Dim i as long
For i = 1 to 10
Range("g15:ax15").Copy Destination:=Range("ea18").Offset(i, 0)
next i
End Sub
//If you need `PasteSpecial` then you cannot use `Destination` hence this version
Sub CopyAndPaste()
Dim i as long
For i = 1 to 10
Range("g15:ax15").Copy
Range("ea18").Offset(i, 0).PasteSpecial Paste:=xlPasteValues
next i
End Sub
Sometimes reading values into an array first and then writing back to the spreadsheet is quicker. Here is an example:
Sub CopyAndPaste()
Dim i As Long, numbers As Variant, rw As Long
numbers = Range("g15:ax15")
rw = 18
For i = 1 To 10
rw = rw + 1
Range(Cells(rw, 131), Cells(rw, 131 + UBound(numbers, 2) - 1)) = numbers
Next i
End Sub
You can do it without copying as yo mention (using a variant array as you are copying values only, not formats)
X = Range("g15:ax15").Value2
[ea18].Offset(1, 0).Resize(UBound(X, 1), UBound(X, 2)).Value2 = X
or with your variable offset
Dim lngCnt As Long
lngCnt = lngCnt + 1
X = Range("g15:ax15").Value2
[ea18].Offset(lngCnt, 0).Resize(UBound(X, 1), UBound(X, 2)).Value2 = X
Row = Row +1
Range("g15:ax15").copy
Range("ea18").Offset(Row,0).PasteSpecial Paste:=xlPasteValues
Select is a more-or-less useless method inherited from recordings.