Paste one array (single dimension) with >100K item into a excel range - vba

I posted the same question on StackOverflow thread
but I think here is the correct place to ask (if is not right, admin please to remove it).Every day I need to format date imported from AS400 (data, time,..).
Usualy (for some thousands of record) I use this code.
Public Sub Cfn_FormatDate(control As IRibbonControl)
Application.ScreenUpdating = False
Dim UR As Long, X As Long
Dim MyCol As Integer
MyCol = ActiveCell.Column
UR = Cells(Rows.Count, MyCol).End(xlUp).Row
For X = 2 To UR
If Not IsDate(Cells(X, MyCol)) Then
Select Case Len(Cells(X, MyCol))
Case 8
Cells(X, MyCol) = DateSerial(Left(Cells(X, MyCol), 4), Mid(Cells(X, MyCol), 5, 2), Right(Cells(X, MyCol), 2))
Case 6
Cells(X, MyCol) = DateSerial(Left(Cells(X, MyCol), 2), Mid(Cells(X, MyCol), 3, 2), Right(Cells(X, MyCol), 2))
End Select
End If
Next X
Columns(MyCol).NumberFormat = "DD/MM/YYYY;#"
Columns(MyCol).EntireColumn.AutoFit
Application.ScreenUpdating = True
End Sub
but if the records are are many more, the code posted code is not performing.
(ex 70K records were formatted / pasted in 18 seconds)
so I thought to using variables in an array and I wrote this code:
Sub ConvDate(c As Integer)
Application.ScreenUpdating = False
Dim lrw As Long, i As Long
Dim ArrVal As Variant
lrw = ActiveSheet().Range(Cells(1, c)).End(xlDown).Row
ReDim ArrVal(2 To lrw)
For i = 2 To lrw
If IsDate(Cells(i, c)) Then
ArrVal(i) = Cells(i, c)
Else
Select Case Len(Cells(i, c)) ' to check YYYYMMDD or YYMMDD
Case 8
ArrVal(i) = DateSerial(Left(Cells(i, c), 4), Mid(Cells(i, c), 5, 2), Right(Cells(i, c), 2))
Case 6
ArrVal(i) = DateSerial(Left(Cells(i, c), 2), Mid(Cells(i, c), 3, 2), Right(Cells(i, c), 2))
End Select
End If
NextX:
Next i
Range(Cells(2, c), Cells(lrw, c)) = ArrVal
Columns(c).NumberFormat = "DD/MM/YYYY;#"
Columns(c).EntireColumn.AutoFit
Application.ScreenUpdating = True
End Sub
It not work, all the cells (in the range) have the same result (Cells(2, c)).
a guy suggested me to change the code like:
ActiveSheet.Range(Cells(2, c), Cells(lrw, c)).Value = WorksheetFunction.Transpose(ArrVal)
this change is limiting , over 65536 records I get an error (runtime 13, type mismatch)

Ok, to summarise all the answers and comments:
As you have indicated in your question and as user85489 alludes, reading the values into an array, manipulating that same array, and writing it back to the sheet is vastly quicker than lopping cell by cell.
If you have an array whose 'row' dimension is not going to change. Then it might be fair to say that you're better off declaring a 2 dimensional array of size (1 to rows, 1 to columns). This way you can avoid having to transpose a 1 dimensional array at all.
Because as Gareth points out, Transpose() is limited to 65536 elements in a dimension.
Putting it all together, then, skeleton code for your post could be this:
Sub ConvertDates(colIndex As Long)
Dim v As Variant
Dim firstCell As Range
Dim lastCell As Range
Dim fullRange As Range
Dim i As Long
Dim dd As Integer
Dim mm As Integer
Dim yy As Integer
Dim dat As Date
'Define the range
With ThisWorkbook.Worksheets("Sheet1")
Set firstCell = .Cells(2, colIndex)
Set lastCell = .Cells(.Rows.Count, colIndex).End(xlUp)
Set fullRange = .Range(firstCell, lastCell)
End With
'Read the values into an array
v = fullRange.Value
'Convert the text values to dates
For i = 1 To UBound(v, 1)
If Not IsDate(v(i, 1)) Then
If Len(v(i, 1)) = 6 Then v(i, 1) = "20" & v(i, 1)
yy = CInt(Left(v(i, 1), 4))
mm = CInt(Mid(v(i, 1), 5, 2))
dd = CInt(Right(v(i, 1), 2))
dat = DateSerial(yy, mm, dd)
v(i, 1) = dat
End If
Next
'Write the revised array and format range
With fullRange
.NumberFormat = "DD/MM/YYYY;#"
.Value = v
.EntireColumn.AutoFit
End With
End Sub

You have come across the 32 bit limitation of the function Transpose which truncates your array to 65536.

You can use loop statement to populate the cells, else if you want to do it directly then define your array ArrVal like:
Redim ArrVal(1,Lrw) as variant
Flood the array with values and then, Offload it like
Range(Cells(2, c), Cells(lrw, c)) = ArrVal
Hopefully you get rid of the same value errors.

Related

Macro for Intelligent Transpose

So currently, per the title, I'm looking to make a smart and relatively automatic transpose system.
So far the only way I've figured out how to do this is with macros, paste special, and a lot of manual work (working on 2,000~ row sheet).
The following example is an example.
All the events belong to A1 but are distributed downwards in a new row. The goal is to have them all in a single row (either in a single cell or adjacent).
A Event 1
A Event 2
A Event 3
B Group 1
B Group 2
All the events belong to A1 but are distributed downwards in a new row. The goal is to have them all in a single row (either in a single cell or adjacent).
The example of how I need them is demonstrate below.
A Event 1 Event 2 Event 3
B Group 1 Group 2
I have searched far and wide and haven't found anything which solves this bizarre request.
You can do this quite easily using a dictionary. Have a look at the following. You will need to update the two With blocks with your input and destination range
Public Sub test()
Dim dict As Object
Dim arr As Variant, tmp As Variant
Dim i As Long
Dim key
Set dict = CreateObject("Scripting.Dictionary")
' Source Data
With Sheet1
arr = .Range(.Cells(1, "A"), .Cells(.Cells(.Rows.Count, "A").End(xlUp).Row, "B")).Value2
End With
For i = LBound(arr, 1) To UBound(arr, 1)
If Not IsEmpty(tmp) Then Erase tmp
If dict.exists(arr(i, 1)) Then
tmp = dict(arr(i, 1))
ReDim Preserve tmp(LBound(tmp) To UBound(tmp) + 1)
tmp(UBound(tmp)) = arr(i, 2)
dict(arr(i, 1)) = tmp
Else
ReDim tmp(0)
tmp(LBound(tmp)) = arr(i, 2)
dict.Add key:=arr(i, 1), Item:=tmp
End If
Next i
' Destination
With Sheet1.Cells(1, 5)
i = 0
For Each key In dict.keys
.Offset(i, 0) = key
'' Side by side
Range(.Offset(i, 1), .Offset(i, UBound(dict(key)) + 1)).Value2 = dict(key)
'' In one cell
'.Offset(i, 1).Value2 = Join(dict(key), ",")
i = i + 1
Next key
End With
End Sub
Say we have data in columns A and B like:
Running this code:
Sub Macro1()
Dim Na As Long, Nd As Long, rc As Long
Dim i As Long, j As Long, K As Long
Dim v As Variant
Range("A:A").Copy Range("D:D")
Range("D:D").RemoveDuplicates Columns:=1, Header:=xlNo
rc = Rows.Count
K = 5
Na = Cells(rc, "A").End(xlUp).Row
Nd = Cells(rc, "D").End(xlUp).Row
For i = 1 To Nd
v = Cells(i, "D")
For j = 1 To Na
If v = Cells(j, 1) Then
Cells(i, K) = Cells(j, 2)
K = K + 1
End If
Next j
K = 5
Next i
End Sub
will produce:

Writing an 2D array to a range of cells

I've got an array of a range of cells and I need to write it back to a specific range of cells. My first 2 columns are working as desired when writing back to the new range of cells but the next 2 columns are mirroring column 2 for columns 3 and 4.
Array Range:
1,2,3,4
2,2,3,5
3,4,5,6
will write as:
1,2,2,2
2,2,2,2
3,4,4,4
What I want is:
1,2,3,4
2,2,3,5
3,4,5,6
Dim myRange As Range
Dim scriptDic As Variant
Dim arr As Variant
Dim i As Integer
Dim x As Integer
With ThisWorkbook.Sheets("AGGREGATE")
Set myRange = .Range("H4:K19")
Set scriptDic = CreateObject("Scripting.Dictionary")
arr = myRange.Value
For i = 1 To UBound(arr, 1)
If arr(i, 1) <> "" Then
scriptDic(arr(i, 1)) = scriptDic(arr(i, 1)) + arr(i, 2)
End If
Next
Application.ScreenUpdating = False
.Range("M4:P19").ClearContents
myRange.Range("F1").Resize(scriptDic.Count, 1) = Application.WorksheetFunction.Transpose(scriptDic.keys)
myRange.Range("G1").Resize(scriptDic.Count, 1) = Application.WorksheetFunction.Transpose(scriptDic.items)
myRange.Range("H1").Resize(scriptDic.Count, 1) = Application.WorksheetFunction.Transpose(scriptDic.items)
myRange.Range("I1").Resize(scriptDic.Count, 1) = Application.WorksheetFunction.Transpose(scriptDic.items)
Application.ScreenUpdating = True
End With
I am assuming that it has to do with this section but I'm not very good with dimensional arrays.
For i = 1 To UBound(arr, 1)
If arr(i, 1) <> "" Then
scriptDic(arr(i, 1)) = scriptDic(arr(i, 1)) + arr(i, 2)
End If
Any help would be much appreciated!
For this purpose, I would get rid of the Dictionary, and just use RemoveDuplicates to obtain the unique key values. Then I would use SUMIF to get the desired answers:
Sub test()
Dim numRows As Long
Application.ScreenUpdating = False
With ThisWorkbook.Sheets("AGGREGATE")
'Clear existing contents of column M:P
.Range("M4", .Cells(.Rows.Count, "M").End(xlUp).Offset(0, 3)).ClearContents
'Copy keys to column M
numRows = .Cells(.Rows.Count, "H").End(xlUp).Row - 3
.Range("M4").Resize(numRows, 1).Value = .Range("H4").Resize(numRows, 1).Value
'Generate unique list
.Range("M4").Resize(numRows, 1).RemoveDuplicates Columns:=1, Header:=xlNo
'Calculate answers in column N to P
numRows = .Cells(.Rows.Count, "M").End(xlUp).Row - 3
.Range("N4").Resize(numRows, 3).Formula = "=SUMIF($H:$H,$M4,I:I)"
'Convert formulas to values
.Range("N4").Resize(numRows, 3).Value = .Range("N4").Resize(numRows, 3).Value
End With
Application.ScreenUpdating = True
End Sub

VBA - Convert multiple delimited columns into multiple row

I was wondering if someone can help me with the following,
In VBA in Excel, I have the following table :
Column 1|Column2|Column3|Column4|Column5|Column6
---------|---------|---------|---------|---------|---------
1.2.3.4|Apple%Car|Canada%USA|Tomatoes|Hotel|Montreal%Paris%New-York
1.3.4.6|Cat%Uniform%Dog|France|Ananas|Motel|Amsterdam%San-Diego
And I would like to convert this in Excel using VBA into the following table :
Column 1|Column 2|Column 3|Column 4|Column 5|Column 6
:---------:|:---------:|:---------:|:---------:|:---------:|:---------:
1.2.3.4|Apple|Canada|Tomatoes|Hotel|Montreal
1.2.3.4|Apple|Canada|Tomatoes|Hotel|Paris
1.2.3.4|Apple|Canada|Tomatoes|Hotel|New-York
1.2.3.4|Apple|USA|Tomatoes|Hotel|Montreal
1.2.3.4|Apple|USA|Tomatoes|Hotel|Paris
1.2.3.4|Apple|USA|Tomatoes|Hotel|New-York
1.2.3.4|Car|Canada|Tomatoes|Hotel|Montreal
1.2.3.4|Car|Canada|Tomatoes|Hotel|Paris
1.2.3.4|Car|Canada|Tomatoes|Hotel|New-York
1.2.3.4|Car|USA|Tomatoes|Hotel|Montreal
1.2.3.4|Car|USA|Tomatoes|Hotel|Paris
1.2.3.4|Car|USA|Tomatoes|Hotel|New-York
1.3.4.6|Cat|France|Ananas|Motel|Amsterdam
1.3.4.6|Cat|France|Ananas|Motel|San-Diego
1.3.4.6|Uniform|France|Ananas|Motel|Amsterdam
1.3.4.6|Uniform|France|Ananas|Motel|San-Diego
1.3.4.6|Dog|France|Ananas|Motel|Amsterdam
1.3.4.6|Dog|France|Ananas|Motel|San-Diego
Does anyone have an idea how to do this ?
Thank you !
To get my brain going I bit. This does more or less what you want (However there is room for improvement as it currently can produce duplicate rows which it then removes at the end. I've missed something but as you haven't tried anything I haven't put any more effort in figuring out where this is happening exactly).
You'll also have to change the Ranges for where your inputs and outputs come from in the ConvertToTable sub. This uses a recursive function (i.e. one that calls itself) to populate your output
Option Explicit
Public Sub ConvertToTable()
Dim data As Variant, tmp() As Variant
Dim arr() As Variant
Dim i As Long
Dim c As Range
With Sheet2
data = Range(.Cells(1, 1), .Cells(2, 6)).Value2
End With
For i = LBound(data, 1) To UBound(data, 1)
tmp = Application.Index(data, i, 0)
arr = PopulateResults(tmp, "%", arr)
Next i
With Sheet4
With .Range(.Cells(1, 1), .Cells(UBound(arr, 2), UBound(arr, 1)))
.Value2 = Application.Transpose(arr)
.RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5, 6), Header:=xlNo
End With
End With
End Sub
Public Function PopulateResults(tmp As Variant, delimiter As String, Results() As Variant) As Variant()
Dim i As Long, j As Long
Dim DelCount As Long, MaxDel As Long
Dim tmp2 As Variant
On Error Resume Next
i = UBound(Results, 2) + 1
If i = 0 Then i = 1
On Error GoTo 0
ReDim Preserve Results(1 To UBound(tmp), 1 To i)
For j = 1 To UBound(tmp)
Results(j, i) = tmp(j)
If InStr(1, tmp(j), delimiter, vbTextCompare) Then
DelCount = 0
Results(j, i) = Split(tmp(j), delimiter)(DelCount)
Do
DelCount = DelCount + 1
tmp2 = tmp
tmp2(j) = Split(tmp(j), delimiter)(DelCount)
Results = PopulateResults(tmp2, delimiter, Results)
Loop Until DelCount = Len(tmp(j)) - Len(Replace(tmp(j), delimiter, vbNullString))
End If
Next j
PopulateResults = Results
End Function
Thank you very much, It is much appreciated. Sorry for the delay, I didn't get any e-mail notification for the response.
I played with the source code and I have the following, it works for all the column that contain short value.. :
'Transform the data
Dim data As Variant, tmp() As Variant
Dim arr() As String
Dim i As Long
Dim c As Range
With Aggregation_Source
data = Range(Cells(1, 1), Cells(2, 8)).Value2
End With
For i = LBound(data, 1) To UBound(data, 1)
tmp = Application.Index(data, i, 0)
arr = PopulateResults(tmp, "%", arr)
Next i
With Aggregation_Source
With Range(Cells(1, 1), Cells(UBound(arr, 2), UBound(arr, 1)))
.Value2 = Application.Transpose(arr)
.RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5, 6, 7), Header:=xlNo
End With
End With
End Sub
Public Function PopulateResults(tmp As Variant, delimiter As String, Results() As String) As String()
Dim i As Long, j As Long
Dim DelCount As Long, MaxDel As Long
Dim tmp2 As Variant
On Error Resume Next
i = UBound(Results, 2) + 1
If i = 0 Then i = 1
On Error GoTo 0
ReDim Preserve Results(1 To UBound(tmp), 1 To i)
For j = 1 To UBound(tmp)
Results(j, i) = tmp(j)
If InStr(1, tmp(j), delimiter, vbTextCompare) Then
DelCount = 0
Results(j, i) = Split(tmp(j), delimiter)(DelCount)
Do
DelCount = DelCount + 1
tmp2 = tmp
tmp2(j) = Split(tmp(j), delimiter)(DelCount)
Results = PopulateResults(tmp2, delimiter, Results)
Loop Until DelCount = Len(tmp(j)) - Len(Replace(tmp(j), delimiter, vbNullString))
End If
Next j
PopulateResults = Results
End Function
Now, I think that the code crash because I have one column that contains two long text separated by % more than a 1000 characters, I will try to change the type for arr() to see if it works but I think I am missing something in the code .

Optimizing excel arrays

I have a very large data set (600,000 rows) of data structured in the following format:
1) There are around 60 products. One is a Total US number, while the others are for Manufacturers and are labled as KMFs. There are also some labeled as PCKGs(but aren't relevant for this question)
2) Each product is located in 60 different markets
3) Each market has 20 different locations
4) I have 12 metrics for which I need to calculate data in the following manner: Total US number - sum(KMFs) for each metric
I have written vba code for this but it is taking too long to run(around 20 minutes) I need to run similar code on at least 20 worksheets. I have tried various methods such as setting screenUpdating etc. to false. Here is my code. I am new to vba coding so I may have missed obvious things. Please let me know anything is unclear. Please help!
Sub beforeRunningCode()
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
ActiveSheet.DisplayPageBreaks = False
End Sub
Sub returnToOriginal()
Application.ScreenUpdating = screenUpdateState
Application.DisplayStatusBar = statusBarState
Application.Calculation = calcState
Application.EnableEvents = eventsState
ActiveSheet.DisplayPageBreaks = displayPageBreaksState
End Sub
Function LastRowFunc(Sheet) As Long
LastRowFunc = ActiveWorkbook.Worksheets(Sheet).Range("A2", Worksheets(Sheet).Range("A2").End(xlDown)).Rows.Count
End Function
Function LastColFunc(Sheet) As Long
With ActiveSheet
LastColFunc = ActiveWorkbook.Sheets(Sheet).Cells(1, .Columns.Count).End(xlToLeft).Column
End With
End Function
Sub AOCalculate()
Call beforeRunningCode 'Optimize Excel
Dim LastRow As Long
Dim LastCol As Long
Dim Period As String
Dim Sheet As String
Dim Arr(1 To 16)
Dim Count As Integer
Sheet = "Energy_LS_Bottler"
Period = "2016 WAVE 1 - 3 W/E 05/07"
LastRow = LastRowFunc(Sheet) 'Calculate last row for which data exists
LastCol = LastColFunc(Sheet) 'Calculate last column for which data exists
For Each Location In ActiveWorkbook.Sheets("Locations").Range("D7:D28").Value
For Each Market In ActiveWorkbook.Sheets("Markets").Range("A5:A92").Value
Count = Count + 1
Arr(1) = Market
Arr(2) = "AO"
Arr(3) = Location
Arr(4) = Period
With ActiveWorkbook.Sheets(Sheet) 'Filtering for KMF
.AutoFilterMode = False
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=17, Criteria1:="=KMF"
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=1, Criteria1:=Market
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=3, Criteria1:=Location
End With
For k = 5 To 16
Arr(k) = Application.WorksheetFunction.Sum(ActiveWorkbook.Sheets(Sheet).Range(Cells(1, k), Cells(LastRow, k)).SpecialCells(xlCellTypeVisible))
Next k
With ActiveWorkbook.Sheets(Sheet) ' filtering for Total US
.AutoFilterMode = False
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=17, Criteria1:="=Total US"
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=1, Criteria1:=Market
.Range(Cells(1, 1), Cells(LastRow, LastCol)).AutoFilter field:=3, Criteria1:=Location
End With
For k = 5 To 16
Arr(k) = -Arr(k) + Application.WorksheetFunction.Sum(ActiveWorkbook.Sheets(Sheet).Range(Cells(1, k), Cells(LastRow, k)).SpecialCells(xlCellTypeVisible))
Next k
For j = 1 To 16
ActiveWorkbook.Sheets(Sheet).Cells(LastRow + Count, j).Value = Arr(j)
Next j
Erase Arr
Next
Next
ActiveWorkbook.Sheets(Sheet).AutoFilterMode = False
Call returnToOriginal
End Sub
[Edit]: Here is a link to a sample data set https://drive.google.com/file/d/0B3MkGa57h6g_WGl2WWlWekd4NU0/view?usp=sharing
I think that this will work (though I haven't had a chance to test it), and should be a lot faster:
Sub AOCalculate()
Call beforeRunningCode 'Optimize Excel
Dim LastRow As Long
Dim LastCol As Long
Dim Period As String
Dim Sheet As String
Dim Arr() '1 To 2000, 1 To 16)
Dim Count As Integer
Sheet = "Energy_LS_Bottler"
Period = "2016 WAVE 1 - 3 W/E 05/07"
LastRow = LastRowFunc(Sheet) 'Calculate last row for which data exists
LastCol = LastColFunc(Sheet) 'Calculate last column for which data exists
'copy all of the relevant cells to local arrays for speed
Dim Locations(), Markets(), data()
Markets = ActiveWorkbook.Sheets("Markets").Range("A5:A92").Value
Locations = ActiveWorkbook.Sheets("Locations").Range("D7:D28").Value
'(pretty sure the following line needs to localize the Cells() to .Cells())
data = ActiveWorkbook.Sheets(Sheet).Range(Cells(1, 1), Cells(LastRow, LastCol)).Value '**'
ReDim Arr(1 To UBound(Markets, 1) * UBound(Locations, 1), 16)
'make an index of pointers into our accumulation array
Dim counts As New Collection
Dim i As Long, l As Long, m As Long
For l = 1 To UBound(Locations, 1)
Location = Locations(l, 1) '**'
For m = 1 To UBound(Markets, 1)
Market = Markets(m, 1) '**'
i = i + 1
counts.Add i, CStr(Location) & "~" & CStr(Market)
'counts.Add NewAccumArray(Location, Market, Period), CStr(Location) & "~" & CStr(Market)
Arr(i, 1) = Market
Arr(i, 2) = "AO"
Arr(i, 3) = Location
Arr(i, 4) = Period
Next
Next
' go through each row and add it to the appropiate count in the array
Dim r As Long
Dim key As String, idx As Long
For r = 1 To UBound(data, 1)
key = CStr(data(r, 3)) & "~" & CStr(data(r, 1))
If data(r, 17) = "KMF" Then
idx = counts(key)
For k = 5 To 16
Arr(idx, k) = Arr(idx, k) - data(r, k)
Next k
Else
If data(r, 17) = "Total US" Then
idx = counts(key)
For k = 5 To 16
Arr(idx, k) = Arr(idx, k) + data(r, k)
Next k
End If
End If
Next r
' output the results
ActiveWorkbook.Sheets(Sheet).Range(Cells(LastRow + 1, 1), Cells(LastRow + Count, 16)).Value = Arr
ActiveWorkbook.Sheets(Sheet).AutoFilterMode = False
Call returnToOriginal
End Sub
Answering the query "What did I mean by this?"
'(pretty sure the following line needs to localize the Cells() to .Cells())
data = ActiveWorkbook.Sheets(Sheet).Range(Cells(1, 1), Cells(LastRow, LastCol)).Value '**'
The use of Cells(..) here is fundamentally unreliable and broken. this is because Cells(..) is really a shortcut for ActiveSheet.Cells(..) and the Active* properties are inherently slow and unreliable because they can change while the code is running. Worse, this code is assuming that ActiveSheet = Energy_LS_Blotter which is far from certain.
The correct way to write this line would be like this:
data = ActiveWorkbook.Sheets(Sheet).Range( _
ActiveWorkbook.Sheets(Sheet).Cells(1, 1), _
ActiveWorkbook.Sheets(Sheet).Cells(LastRow, LastCol) _
).Value
But that is long, ugly and inconvenient. An easier way would be to use either a Sheet variable, or a With:
With ActiveWorkbook.Sheets(Sheet)
data = .Range(.Cells(1, 1), .Cells(LastRow, LastCol)).Value
End With

Optimization for Excel

Here is the Situation: In my Excel sheet I had a column with entries in the form 1-name. I wanted to remove the numbers, taking into account that the number can also be double digit. This by itself was not a Problem and I got it working, just the Performance is so bad. As it is now my program needs about half a second per cell entry.
My question: How can I improve the performance?
Here is the code:
Sub remove_numbers()
Dim YDim As Long
Dim i As Integer, l As Integer
Dim val As String
Dim s As String
YDim = Cells(Rows.Count, 5).End(xlUp).Row
For i = 8 To YDim
val = Cells(i, 5)
l = Len(val)
s = Mid(val, 2, 1)
If s = "-" Then
val = Right(val, l - 2)
Else
val = Right(val, l - 3)
End If
Cells(i, 5).Value = val
Next i
End Sub
Instead of using 3 different functions: Len(), Mid(), Right() you could use a Split() function which would have been much more efficient in this case.
Try the below code
Sub remove_numbers()
Application.ScreenUpdating = False
Dim i As Long
For i = 8 To Cells(Rows.Count, 5).End(xlUp).Row
Cells(i, 5) = Split(Cells(i, 5), "-")(1)
Next i
Application.ScreenUpdating = True
End Sub
My suggestion:
Sub remove_numbers()
Dim i As Integer, values() As Variant
values = Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp).Row).Value
For i = LBound(values) To UBound(values)
values(i, 1) = Mid(values(i, 1), IIf(Mid(values(i, 1), 2, 1) = "-", 2, 3))
Next
Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp).Row).Value = values
End Sub
Optimizations:
Perform all calculations in memory and them update entire range: this is a HUGE performance gain;
Condensed multiple commands into a single command;
Replaced Right(x, Len(x)-n) with Mid(x, n).
EDIT:
As suggested by #Mehow, you may also gain some performance using
values(i, 1) = Split(values(i, 1), "-", 2)(1)
instead of values(i, 1) = Mid(values(i, 1), IIf(Mid(values(i, 1), 2, 1) = "-", 2, 3))
You should manipulate the whole range values as an array and work directly with it in memory.
Something like :
Dim valuesOfRangeToModify() As Variant
Set valuesOfRangeToModify = Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp)).Value
For Each cell In valuesOfRangeToModify
cell = ... // remove numbers
Next
Range(Cells(8, 5), Cells(Rows.Count, 5).End(xlUp)).Value = valuesOfRangeToModify
My VB is quite old so it probably has syntax errors but you get the idea.
This should give a huge boost.
For reference, here is an article full of interesting advices, see point #4 for more explanation of the solution given above :
http://www.soa.org/news-and-publications/newsletters/compact/2012/january/com-2012-iss42-roper.aspx
Also do not operate one cell at a time. Create a range of cells and transfer them into an array for processing. In the end the array can be used to replace the cells.
To tweak the answer from #mehow
Sub remove_numbers()
Dim i As Long, N as Long, r as Range
Set r = Range("B3") ' Whatever is the first cell in the column
N = Range(r, r.End(xlDown)).Rows.Count 'Count the rows in the column
Set r = r.Resize(N,1) ' Expand the range with all the cells
Dim values() as Variant
values = r.Value ' Collect all the values from the sheet
For i=1 to N
values(i,1) = Split( values(i,1), "-")(1)
Next i
r.Value = values 'Replace values to the sheet
End Sub
To make it more general you can add an argument to the procedure to pass a reference to the first cell in the column, like Sub remove_numbers(ByRef r as Range). There is no need to de-activate the screen as there is only one write operation in the end and you want the screen to update after that.