Welcome to the MacNN Forums.

If this is your first visit, be sure to check out the FAQ by clicking the link above. You may have to register before you can post: click the register link above to proceed. To start viewing messages, select the forum that you want to visit from the selection below.

You are here: MacNN Forums > Software - Troubleshooting and Discussion > Developer Center > Live word count implementation glitches (Cocoa)

Live word count implementation glitches (Cocoa)
Thread Tools
Fresh-Faced Recruit
Join Date: Jul 2004
Status: Offline
Reply With Quote
Aug 13, 2004, 12:34 PM
 
Hello,

Although I am still fairly new to Cocoa, I thought I would try my hand at a live word count for a text view (yes, I am ploughing through some Cocoa books to learn properly ). I am using NSScanner to scan up to the whitespace and newline character set to do this - that part is based on some code I found in the mail archives and works quite nicely (I do need to refine it a little, for instance by using formUnionWithCharacterSet to stop it counting hyphens and so forth as words, but that is fairly straightforward). At first I calculated my word count for the whole text view every time the text was edited using the textDidChange delegate method, updating the count in a text field. This worked fine up to a point, but as you might expect, as soon as large chunks of text were pasted into the text view (ie. 10,000 words+), performance took a major hit because of the scanner having to parse so many characters.

I therefore want to implement a more efficient method, and Chuckit on this forum very helpfully suggested that using "editable blocks" would help optimise a word count. I thought about this, and figured that I could limit the word count to calculating lines that are being edited on by using the following approach:
1) Keep a word count as an instance variable that keep tally of all changes.
2) Whenever text is about to change, count the words in the line(s) containing the text that is about to be changed (using lineRangeForRange) *before* it is changed using textView:shouldChangeTextInRange:replacementString :, and subtract this number from the running total.
3) After the text has changed, count the words in the line(s) that just changed using textDidChange: and add this number to the running total.

I implemented this (code below), and it is almost working perfectly - almost. I can paste in over 100,000 words of text and there is no performance hit, bacause parsing is only ever done on the line currently being worked on (problems would only occur if someone wanted to write a single paragraph of over 10,000 words...). However, my code is still giving me some problems. The main problem is that if I delete blank lines (ie. containing invisible newline characters), the word count seems to go *up*. For instance, if I typed in my text view:

And now

this

The word count gives 3, as it should. But if I then place the cursor at the beginning of "this" and hit backspace, so that the text looks like this:

And now
this

the word count goes *up* to 4. If I then hit backspace again to put it all on one line, like this:

And nowthis

the word count remains at 4. If I then hit space, so that it reads "And now this", the word count goes *up* again to 5. If I then delete the whole line, the word count remains at 2, even though there is nothing there. I sat down and worked all this out on paper, and realised that there is some skewed logic in my code, as in the above case it works like this:

1) Calculate word count of affected line about to be changed = blank line = 0
2) Delete this from running total -> total unchanged
3) Calculate word count of the line containing the text that has replaced it = "this" = 1
4) Add that to the running total -> 1 is added to the total because the "this" line was counted again

Unfortunately, I have been staring at this for two days now and cannot find a solution that works. I tried asking on the Apple mailing list, but didn't have much joy. I know that I somehow need to expand the blocks that are being counted to cover all the affected lines, but just can't seem to find a way of doing it. Has anybody got any suggestions?

If anyone can cast some fresh eyes on this code and point out the errors in my logic I would be very grateful. Sorry for the long mail.

Many thanks,
KB

Code:
/* // Relevant instance variables: IBOutlet NSTextField wordCountText; // text field for displaying word count int words; // word count running total (initialized to 0) NSRange changedTextRange; // for storing the range of the edited string (initialized to {0,0}) */ -(unsigned) wordCountForString: (NSString *) textString range: (NSRange) range { NSString *lineString = [textString substringWithRange: range]; // WORD COUNT NSScanner *wordScanner = [NSScanner scannerWithString: lineString]; NSCharacterSet *whiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; unsigned wordCount = 0; while ([wordScanner scanUpToCharactersFromSet: whiteSpace intoString: nil]) { wordCount++; } return wordCount; } // DELEGATE METHODS -(BOOL) textView: (NSTextView *) aTextView shouldChangeTextInRange: (NSRange) affectedCharRange replacementString: (NSString *) replacementString { // Get the line that is currently being worked on: NSRange currLineRange = [[aTextView string] lineRangeForRange: affectedCharRange]; // Now subtract the number of words in that line from the running total: if (currLineRange.length > 0) // ...but only if there is something to subtract { words -= [self wordCountForString: [aTextView string] range: currLineRange]; } // Now calculate the range of replacementString so that we can calculate the new // word count of the line after text has changed... changedTextRange = NSMakeRange (affectedCharRange.location, [replacementString length]); return YES; } -(void) textDidChange: (NSNotification *) notification { // Get the line(s) currently being worked on: NSRange currLineRange = [[mainTextView string] lineRangeForRange: changedTextRange]; // Now add the number of words in the line to the running total: if (currLineRange.length > 0) // ...but only if there is something to add { words += [self wordCountForString: [mainTextView string] range: currLineRange]; } [wordCountText setIntValue: words]; }
     
Ambit  (op)
Fresh-Faced Recruit
Join Date: Jul 2004
Status: Offline
Reply With Quote
Aug 13, 2004, 05:02 PM
 
Just in case anybody has the time or patience to check it out, I have thrown together a quick test project (not many lines of code at all) to show the problematic live word count, which can be downloaded here:

http://www.rumramruf.com/wordCountTest.zip

Thanks!
KB
     
Clinically Insane
Join Date: Oct 2001
Location: San Diego, CA, USA
Status: Offline
Reply With Quote
Aug 13, 2004, 10:56 PM
 
I'm too tired to code right now, but I thought I'd let you know that what you linked there does not appear to be a project; it's a ZeroLinked executable that can't be run outside of your computer. So not only can't I debug it, I can't even open it.
Chuck
___
"Instead of either 'multi-talented' or 'multitalented' use 'bisexual'."
     
Ambit  (op)
Fresh-Faced Recruit
Join Date: Jul 2004
Status: Offline
Reply With Quote
Aug 14, 2004, 04:56 AM
 
Hello, thanks for your reply, ChuckIt. Yes, you're right. I was so tired when I created the test project last night that I zipped up the build instead of the project itself. Duh. The link above should now point to a zip file that contains the actual project so that anyone interested or willing to take a look can open the source code and build it for themselves.

Many thanks again for trying to take a look,
KB
     
   
Thread Tools
Forum Links
Forum Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On
Top
Privacy Policy
All times are GMT -5. The time now is 01:08 PM.
All contents of these forums © 1995-2011 MacNN. All rights reserved.
Branding + Design: www.gesamtbild.com
vBulletin v.3.8.7 © 2000-2011, Jelsoft Enterprises Ltd., Content Relevant URLs by vBSEO 3.3.2