Trimming cells, before and after


September 16, 2008 – 10:16 am by JP

    As an experiment, I decided to rewrite one of my old routines that I use a lot, but hadn't re-thought in a while -- a simple sub that trims worksheet cells. I plan to do that more often in the coming weeks, because a lot of my code is old and written quick and dirty to get something done, sometimes by brute force.

    I get a lot of poorly formatted worksheets, and as you know, if a cell contains hidden spaces at the end, then string comparisons (i.e. "=IF(A1=B1)") will fail. This is maddening at times, so originally I wrote this routine:

VBA:
  1. Sub Trim_Cells()
  2. Dim cell As Excel.Range
  3.  
  4. If Selection.Cells.Count> 1 Then
  5.     Selection.SpecialCells(xlCellTypeConstants).Select
  6. End If
  7.  
  8. With WorksheetFunction
  9.     For Each cell In Selection
  10.         cell = Trim(cell)
  11.         cell = .Trim(cell)
  12.     Next cell
  13. End With
  14.  
  15. ActiveCell.Select
  16. End Sub

    With a shortcut key or toolbar button, it's very convenient. And as you might be able to tell by looking at it, very slow. To speed it up somewhat, it limits the action cells to just constants, but the VBA code still has to hit the worksheet in a 1:2 ratio (!) for each cell in the selection, because for good measure I called the VBA Trim function as well as the Worksheetfunction version. That means each cell has to be hit twice!

    I inserted the code above into a generic Timer sub I use to for testing purposes, and ran it on a range of 500 cells. See below for test results.

VBA:
  1. Sub TimerTest()
  2. '
  3. ' place your test code between the starttime and endtime variables
  4. '
  5. StartTime = Timer
  6. '
  7. Dim cell As Excel.Range
  8.  
  9. If Selection.Cells.Count> 1 Then
  10.     Selection.SpecialCells(xlCellTypeConstants).Select
  11. End If
  12.  
  13. With WorksheetFunction
  14.     For Each cell In Selection
  15.         cell = Trim(cell)
  16.         cell = .Trim(cell)
  17.     Next cell
  18. End With
  19.  
  20. ActiveCell.Select
  21. '
  22. EndTime = Timer
  23. MsgBox ("This routine took " & (EndTime - StartTime) & " seconds.")
  24. '
  25. '
  26. '
  27. End Sub

theoldway

    As you can see, this is barely tolerable. In a large worksheet, unacceptable. So on a lark I rewrote the code to use arrays exclusively. Here it is:

VBA:
  1. Sub Trim_Cells_Array_Method()
  2.  
  3. Dim arrData() As Variant
  4. Dim arrReturnData() As Variant
  5. Dim rng As Excel.Range
  6. Dim lRows As Long
  7. Dim lCols As Long
  8. Dim i As Long, j As Long
  9.  
  10. lRows = Selection.Rows.Count
  11. lCols = Selection.Columns.Count
  12.  
  13. ReDim arrData(1 To lRows, 1 To lCols)
  14. ReDim arrReturnData(1 To lRows, 1 To lCols)
  15.  
  16. Set rng = Selection
  17. arrData = rng.Value
  18.  
  19. For j = 1 To lCols
  20.   For i = 1 To lRows
  21.     arrReturnData(i, j) = Trim(arrData(i, j))
  22.   Next i
  23. Next j
  24.  
  25. rng.Value = arrReturnData
  26.  
  27. Set rng = Nothing
  28. End Sub

    When this code is plugged into the TimerTest sub, here is the result:

thenewway

    By using arrays and limiting the number of hits on the worksheet (i.e. limiting the interaction between Excel and VBA), we are able to produce a 1,000,000% increase in speed! Seriously, the lesson I've learned here is:

a) Read all your worksheet data into an array up front, try to manipulate it in VBA exclusively (no Worksheetfunction calls or temporary worksheets), and dump it back to the worksheet in one shot. Your watch will thank you.
a) Loops aren't intrinsically bad. It's the loops that require repeated hits on the worksheet (especially 1:1 ratio 'For Each x in Selection' type loops) that are costly. As I've mentioned before, when VBA and Excel have to interact, there is a time penalty.

Enjoy,
JP


Share and Enjoy:
  • StumbleUpon
  • Technorati
  • Digg
  • Google
  • del.icio.us
  • MisterWong

Print This Post Print This Post  |  Email This Post Email This Post  |  Permalink  |  Subscribe to this feed Subscribe now!

Filed Under: Excel, VBA, arrays
Tags: ,

This post has 149 views since September 16, 2008 – 10:16 am.
  1. 3 Responses to “Trimming cells, before and after”

  2. Between the timer statements and your code, insert these statements:

    Application.ScreenUpdating = False ' beginning

    Application.ScreenUpdating = True ' end

    I'll bet this comes in closer to the array approach than the original worksheet approach.

    By Jon Peltier on Sep 16, 2008

  3. Well met, Jon!

    I added the ScreenUpdating option and here was the result:

    Old way: .125 seconds
    New way: .01 seconds

    I also added 'Calculation = xlCalculationManual' and got even more improvement from the original method:

    Old way: .11 seconds
    New way: .01 seconds

    There was an additional slight improvement when calculation was turned off as well. The array method wasn't really helped by turning calculation off, though; my guess is because the worksheet isn't being touched that much in the first place, so not much recalculation was taking place anyway.

    Thx,
    JP

    By JP on Sep 17, 2008

  4. The array technique should be touching the worksheet twice, once to read and once to write, and reading is much faster than writing. The looping technique is more interactive, with one read and write per cell. Turning off Screen Updating and Caluclation will have a large effect on the loop, since these make each of many I/O operation faster. In the array, these steps make each of one operation faster.

    By Jon Peltier on Sep 17, 2008

Post a Comment

To post VBA code in your comment, use [VBA] tags, like this: [VBA]Code goes here[/VBA].





Subscribe without commenting

Keep Reading:

Browse Posts:


« Excel games || Excel Class Scheduler »