3

I am writing a text editor and need to provide a live word count. Right now I am using this extension method:

 public static int WordCount(this string s)
    {
        s = s.TrimEnd();
        if (String.IsNullOrEmpty(s)) return 0;
        int count = 0;
        bool lastWasWordChar = false;
        foreach (char c in s)
        {
            if (Char.IsLetterOrDigit(c) || c == '_' || c == '\'' || c == '-')
            {
                lastWasWordChar = true;
                continue;
            }
            if (lastWasWordChar)
            {
                lastWasWordChar = false;
                count++;
            }
        }
        if (!lastWasWordChar) count--;
        return count + 1;
    }

I have it set so that the word count runs on the richtextbox's text every tenth of a second (if the selection start is different from what it was last time the method ran). The problem is that the word count gets slow when working on very long files. To solve this I am thinking about having the word count only run on the current paragraph, recording the word count each time and comparing it against what the word count was last time the word count ran. It would then add the difference between the two to the total word count. Doing this would cause many complications (if the user pastes, if the user deletes a paragraph, ect.) Is this a logical way to go about improving my word count? Or is there something that I don't know about which would make it better?

EDIT: Would it work to run the word count on a different thread? I don't know much about threading, will research.

SAMPLE TEXT THAT I USED:

Justin
  • 2,399
  • 4
  • 31
  • 50

3 Answers3

12

You could do a simpler word count based on white-space:

public static int WordCount(this string s)
{
  return s.Split(new char[] {' '}, 
    StringSplitOptions.RemoveEmptyEntries).Length;
}

MSDN provides this example, should give you an accurate word count much faster on large files.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • Thank you for your response, but this method is not accurate enough. Just now I tested both the method in my original post and the method in this post in a document which, according to Microsoft Word, is 2,142 words long. The method in this post counts 2,165 and the method in my original post counts 2,141. – Justin Feb 09 '10 at 02:30
  • @highone - Post that sample chunk of text...I'll see what the difference is, it's possible with a slight adjustment we can match it up. – Nick Craver Feb 09 '10 at 02:53
  • I had to post the text as an answer since it was too big to fit in a comment. I am new to stackoverflow and am not sure if I should have posted it as a community wiki or not. – Justin Feb 09 '10 at 02:59
  • @highone - I updated the answer, I'm getting 2,142 with that method...see it if works for you. – Nick Craver Feb 09 '10 at 03:22
  • @highone - I did some benchmarking and this method is minimum twice as fast as any other method. If speed is your main requirement, then this is the method you're going to want to use... +1 – John Rasch Feb 09 '10 at 04:24
  • @Nick Craver This method is a lot faster than the method I am using right now. However it is still too inaccurate. It sometimes counts punctuation as words. I tested it on an update of the test text and got 2,062 on a file that word says is 1,099 the method I am using right now counted 1,098 (after a slight change). I have updated my question with the updated test text and the update to the method I am using right now. – Justin Feb 10 '10 at 01:17
  • @highone - On the text in your current question, I got 2,098 from this method, 2,098 with your method and 2,099 from Word. Can you double check your results? (I did fix the variable in mine though, s != str, woops.) – Nick Craver Feb 10 '10 at 01:30
  • @Nick Craver- I apologize, I have no Idea what I was doing wrong. However the problem with punctuation remains. I'm thinking I will try to find a way to do it on a separate thread. – Justin Feb 10 '10 at 02:50
  • @highone - Can you give me some punctuation examples that are breaking? There may be an easy fix. – Nick Craver Feb 10 '10 at 03:13
  • @Nick Craver- This string returns 4 words "< . . ."" it isn't a huge flaw, but it is a little annoying when it counts non-alphanumeric characters as words. – Justin Feb 10 '10 at 20:27
  • @highone - Apologies, thought you wanted the same behavior as microsoft word...you have a list of what you wouldn't count as a word? – Nick Craver Feb 10 '10 at 20:43
  • @Nick Craver- I must apologize, I did not realize that microsoft word counted words that way. If it's good enough for Word it's good enough for me. Thank you very much for all your help! – Justin Feb 10 '10 at 20:46
  • I tried this, and it outputted 1,133 words.. That should be right. But I went to two different online word counters and they both return 1,196 words.. Is there a problem with the code, or does it vary between counters? – Momoro Mar 27 '20 at 03:14
4

You could also use a very simple Regex that looks for at least one word character and/or apostrophe to capture the contractions:

public static int WordCount(this string s) 
{
    return Regex.Matches(s, @"[\w']+").Count;
}

This will return 2141 matches (which is actually more correct than Word in this case because Word counts the single asterisk as a word in the sentence "by stabbing a * with her finger").

John Rasch
  • 62,489
  • 19
  • 106
  • 139
  • Unfortunately regex is much slower than the method I am currently using. – Justin Feb 10 '10 at 01:26
  • 1
    @highone - I pointed this out in the comments of Nick's answer. You have a tradeoff, do you want to sacrifice some speed for accuracy? As I mentioned above, even Word is incorrect when it comes to certain punctuation. It all depends on what you want. Split will always be the fastest method unless it's a new document and you keep track of the word count as the user types. – John Rasch Feb 10 '10 at 01:53
  • What exactly do you mean by keeping track of the word count as the user types? – Justin Feb 10 '10 at 02:52
0

Your method is actually faster than the proposed String.Split method, nearly three times faster on x86 and more than two times faster on x64 in fact. I suspect JIT is messing with your timings, always run your microbenchmarks twice as JIT will occupy the vast majority of the time during your first run. And because String.Split has been NGEN'd, it doesn't need to be compiled to native code and thus will appear to be faster.

Not to mention it's also more accurate, String.Split will count 7 words here:

test :     : this is a test

It also makes sense, String.Split doesn't perform any magic and I would be very surprised if the creation of an array of many strings would be faster than simply iterating over the individual characters in the string. Foreaching over a string has apparently been highly optimized as I tried unsafe pointer arithmetic and it was actually a tiny bit slower than a simple foreach. I really doubt there's any way to do this faster, other than being smart about which sections in your text need word counts.

JulianR
  • 16,213
  • 5
  • 55
  • 85