Tech Eval

G

Greg Maxey

Masters,

Earlier today I helped and OP looking for a way to look at each line in the
document and "If" the first character was not a space to assign a different
style. This is what I proposed:

Sub ScratchMacro()
Dim oPar As Paragraph
Dim myRange As Range
For Each oPar In ActiveDocument.Paragraphs
Set myRange = oPar.Range
If Not myRange.Characters.First = " " Then
oPar.Style = "Body Text"
End If
Next
End Sub

I continued playing with this question and came up with this:

Sub Test()
Dim oPar As Paragraph
Dim myString As String
For Each oPar In ActiveDocument.Paragraphs
myString = oPar.Range.Text
If Not Left(myString, 1) = " " Then
oPar.Style = "Body Text"
End If
Next
End Sub

Both seem to work equally well on the limited sample text that I tested
with. My question is which, if either, is the more appropriate and why?
Thanks.
 
J

Jay Freedman

Hi Greg,

I don't think there's any practical difference between the two approaches.
If you're processing a document with thousands of paragraphs, though, this
might be quicker:

Sub Test()
Dim oPar As Paragraph
For Each oPar In ActiveDocument.Paragraphs
If Left(oPar.Range.Text, 1) <> " " Then
oPar.Style = "Body Text"
End If
Next oPar
End Sub

That is, you don't have to assign the .Text to a string before you feed it
into the Left function, as VBA will create an unnamed temporary variable for
the parameter. I've found that accessing the Characters collection of a range
is far slower than anything else.
 
W

Word Heretic

G'day Jay Freedman <[email protected]>,

What is even faster is to use something like this

Collapse mypara to end
Extend to next para end

if one char then end of doc

it maintains a LINEAR access speed, unlike the for each which has to
dynamically calculate the next para FROM THE START.

On documents <1200 paras this makes a little difference, on large
documents it makes a HUGE difference - like HOURS quicker. 1200 real
world paras seems to be the rough point at which you can definitively
see a major slowdown in code execution iterating collections.

My reverse engineering of this thus says Word has some sort of buffer
for content, it is very large, but finite. So for 1200 paras we have
to also keep reloading the correct buffer after say 1024 paragraphs,
thus it only takes a few hundred iterations of this (1025-1200) to
show up on your performance graphs as a steeper gradient at this
point.

This makes business sense as it enables faster document processing for
the much more frequently occuring smaller documents, whilst still
allowing for the rarer large documents to exist. 80/20 right?

This has also been platform independant - in so far as different CPUs
and memory sizes on the host system made no difference to the
findings.

So, by doing it the heretical way, the number of paragraphs scanned
for their content to 'serialise' them is equivalent to the paragraphs
count, instead of the factorial of the count as is the case with any
dynamic collection based method (chars / paras / whatever).

Dynamic method:

This is my #1 trade secret for writing ultra fast macros. Don't forget
it! :) It only takes about 30 mins for a reasonably competent user to
do a basic check of these assertions if they are so inclined.

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Jay Freedman reckoned:
 
G

Greg Maxey

Steve

Do you mean something like this:
Sub Test1()
Dim myPara As Range
Set myPara = ActiveDocument.Paragraphs(1).Range
Do Until Len(myPara) = 1
If Left(myPara.Text, 1) <> " " Then
myPara.Style = "Body Text"
End If
myPara.Collapse Direction:=wdCollapseEnd
myPara.Expand Unit:=wdParagraph
myPara.Select
Loop
End Sub

The only way I could figure out how to break out of the loop was an empty
paragraph at the end of the document and then the Len(myPara) condition. I
know that there has to be a better way, but I am stumped.
 
G

Greg Maxey

Steve,

I cobbled this together. Does it defeat the speed advantage you proposed?

Sub Test1()
Dim myPara As Range
Dim i As Long
Set myPara = ActiveDocument.Paragraphs(1).Range
i = 0
Do
i = i + 1
If Left(myPara.Text, 1) <> " " Then
myPara.Style = "Body Text"
End If
myPara.Collapse Direction:=wdCollapseEnd
myPara.Expand Unit:=wdParagraph
Loop Until i = ActiveDocument.Paragraphs.Count
End Sub
 
G

Greg Maxey

Well I must be missing something Steve. I tried both of my attempts to use
your suggestions on a several thousand paragraph document and the result was
an order of magnitude slower. I suppose it has something to do with my exit
strategy.
 
W

Word Heretic

G'day "Greg Maxey" <[email protected]>,

Yes - it does. However, it is no good for when you are EDITING content
and the length or number of paragraphs may change. Which is why your
base 'marker' has to be a range. I have sent an article submission
directly to you in explanation.

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Greg Maxey reckoned:
 
W

Word Heretic

G'day "Greg Maxey" <[email protected]>,

At least you are making considerable progress and efforts :) It is
the next best thing.

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Greg Maxey reckoned:
 
W

Word Heretic

G'day "Greg Maxey" <[email protected]>,

I was misleading in my len=1, any blank para will stop that loop. I
test the range end against the current storyrange end.

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Greg Maxey reckoned:
 
W

Word Heretic

G'day "Greg Maxey" <[email protected]>,

See previous reply.

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Greg Maxey reckoned:
 
G

Greg Maxey

Steve,

I see your point. I ran the following two macros on a document containing
100,000 single line paragraphs. Your method was over 20% faster:

Sub UsualWay()
'Time to process 100,000 single line paragraphs 85.79 seconds
Dim StartTime As Single
Dim aPara As Paragraph
StartTime = Timer
With ActiveDocument.StoryRanges(wdMainTextStory)
For Each aPara In .Paragraphs
aPara.Range.HighlightColorIndex = HColor
Select Case HColor
Case wdRed
HColor = wdNoHighlight '0
Case wdNoHighlight
HColor = wdBlue '2
Case wdBlue
HColor = wdRed '6
End Select
Next aPara
End With
Set aPara = Nothing
MsgBox "Time taken was: " & (Timer - StartTime) & " seconds"
End Sub

Public Sub FasterWay()
'Time to process 100,0000 single line paragraphs 69.45 seconds
Dim StartTime As Single
Dim aPara As Range
StartTime = Timer
With ActiveDocument.StoryRanges(wdMainTextStory)
Set aPara = .Paragraphs(1).Range
Do
aPara.HighlightColorIndex = HColor
Select Case HColor
Case wdRed
HColor = wdNoHighlight '0
Case wdNoHighlight
HColor = wdBlue '2
Case wdBlue
HColor = wdRed '6
End Select
aPara.Collapse wdCollapseEnd
aPara.MoveEnd wdParagraph, 1
Loop Until aPara.End = .End
End With
Set aPara = Nothing
MsgBox "Time taken was: " & (Timer - StartTime) & " seconds"

End Sub
 
W

Word Heretic

G'day "Greg Maxey" <[email protected]>,

I can assure Real Life documents are even more dramatic for the
reasons explained in the article :)

By the way Greg - you have just demonstrated your competency as the
article claims a reasonably competent user can easily prove the
principles inside half an hour...

<evil grins>

We will learns ya!

Steve Hudson - Word Heretic

steve from wordheretic.com (Email replies require payment)
Without prejudice


Greg Maxey reckoned:
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top