4
\$\begingroup\$

I've two working ways to do so, but which one should I use?

Common part: var textarea = document.getElementById("textarea");

First way:

function updateStatusBar() {
    var text = textarea.value;
    statusBar.value = "Words: " + (text ? text.match(/\b\S+\b/g).length : "0") +
      "  Characters: " + text.replace(/\s/g, "").length +
      " / " + text.replace(/\n/g, "").length;
}

and second way:

function updateStatusBar() {
    var text = textarea.value;
    statusBar.value = "Words: " + (text.split(/\b\S+\b/).length - 1) +
      "  Characters: " + text.replace(/\s/g, "").length +
      " / " + text.replace(/\n/g, "").length;
}

Please review the Words: counting code. Which one should I use?

\$\endgroup\$
  • \$\begingroup\$ I would strongly recommend you, to count your characters by using something like: textarea.innerText.length \$\endgroup\$ – user33722 Mar 3 '14 at 10:34
  • \$\begingroup\$ newlines aren't characters. \$\endgroup\$ – annn Mar 3 '14 at 11:05
  • \$\begingroup\$ note that if you are doing this for textarea input validation, there is a native character counter in HTML5. Granted, characters aren't necessarily words. See developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text \$\endgroup\$ – Ron Royston Jan 7 at 0:39
4
\$\begingroup\$

The key difference between the two approaches is essentially these lines, counting the words in text:

text ? text.match(/\b\S+\b/g).length : "0"
// ... versus ...
text.split(/\b\S+\b/).length - 1

First of all, the first expression will crash for a non-empty text without words, for example :!@#$. Because a non-empty text is "true", but the .match will return null, so you'll get a null pointer exception in .length.

Secondly, I suggest a simpler and more intuitive regular expression to match words:

/\w+/g

That is, match a non-empty sequence of word characters.

You could use this as text.match(/\w+/g) (notice the "g" flag) or as text.split(/\w+/). When using match, you need to check if the result is null or not, as you already did.

As for which way is better, using match or split, I would argue for match:

  • It's more intuitive: it matches the character sequences you're interested in, and then count the occurrences.
  • It's probably more efficient: splitting implies creating an array of the results, but if you only need the size of the array (the count of elements), then it sounds like a waste.
\$\endgroup\$
  • \$\begingroup\$ I want a solution. returns "2 words", i.e. it don't work. See codepen.io/rhroyston/pen/ebrveB. I came up with the answer below. ...It boiled down to handling multiple spaces, tabs, and end-of-sentence characters such as a period, . \$\endgroup\$ – Ron Royston Jan 7 at 0:29
  • \$\begingroup\$ @RonRoyston As I wrote, I intentionally excluded words that would be single-letter. So getting 2 words for "I want a solution." is no surprise. This is a very old post, and I don't remember anymore why I wanted to exclude such words. I rewrote it now, thanks for bringing this to my attention. \$\endgroup\$ – janos Jan 7 at 21:12
  • \$\begingroup\$ honestly, that \W\w+, or whatever regex you originally had was quite a stunner to me. Very nice. Simple always wins. ...however, a word counter has gotta account for words and ignore characters that are not words. ...in any case, appreciate the input. \$\endgroup\$ – Ron Royston Jan 7 at 22:26
1
\$\begingroup\$

A few comments on this...

What is a character? In your code, you are only counting non-spaces as characters. But, if the user enters a a that counts as 10 characters to me.....

From my perspective, Characters can just be text.length.

Still your definition appears to be 'non-space characters'. Using that definition....

Now, about the regex. You describe 2 ways to count words, and one way to count non-space characters, and then, for some odd reason, you count newlines as well.

So, if I were to suggest that the best way to do it was with just one big, and few small regex... ? The big regex is the most complicated to run because it needs to do more complicated matching on a larger value. By stripping the value sooner, you can make it faster.

Note, you do not need the \b word boundary markers when dealing with either \s+ or \S+.

//Function declaration, will be hoisted for 'addEventListener'
//Most of the work is done to have this work for multiple text area's
function updateStatus(){

  var text = this.value,
      // replace all words with an x
      xWords = text.replace(/\S+/g, "x"),
      //Replace those x's
      noWords = xWords.replace(/x/g, ""),
      //Get rid of newlines from just the spaces.
      noNewLines = noWords.replace(/\n/g, "");
  //You could consider a template function here..
  statusBar.textContent = "Length: " + text.length +
                          " Words: " + (xWords.length - noWords.length) +
                          " Characters: " + (text.length - noWords.length) +
                          " / " + (noWords.length - noNewLines.length);  
}

The above creates successively smaller string values, and compares the difference in length to compute the result....

Sometimes Plan C is the better option.

With the help of Konijn we/I have put together this jsfiddle which shows it in operation.

\$\endgroup\$
  • \$\begingroup\$ newline is not a character and . is not a word (put a . on a new line). \$\endgroup\$ – Ron Royston Jan 7 at 0:33
1
\$\begingroup\$
  1. Replace tab and newline and sentence ending characters, e.g. ., with space character.
  2. Split the resulting string on space character.
  3. .trim() each item and .push to new array if it is not an empty string.

The .length of that new array provides your word count. See it in action here/below (test by adding several spaces between words and by using several line breaks):

document.querySelector("textarea").addEventListener('keyup', count);

function count(){
  var resultArray = [];
  var str = this.value.replace(/[\t\n\r\.\?\!]/gm,' ');
  var wordArray = str.split(" ");
  for (var i = 0; i < wordArray.length; i++) {
    var item = wordArray[i].trim();
    if(item.length > 0){
      resultArray.push(item);
    }
  }
  document.querySelector("span").innerText = resultArray.length;
}
<textarea rows="8" cols="40"></textarea>
<p><span>0</span> words.</p>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.