5
\$\begingroup\$

I wrote a program to implement to find the count of words for a given string.

I have applied two different approaches.

The test works for these two approaches and I am looking for other approaches to solve this problem.

Thanks for your valuable comments advance.

package test;

import main.algorithms.WordCount;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class WordCountTest {

    WordCount wc;

    @Before
    public void setUp(){
        wc = new WordCount();
    }

    @Test
    public void testWordCount(){

        String str = "nesly amazon google facebook apple";

        Assert.assertEquals(5,wc.countWords(str));

        Assert.assertEquals(5,wc.countWordsTokenizer(str));
    }

}


package main.algorithms;

import java.util.StringTokenizer;

public class WordCount {

    public int countWords(String str)
    {
        int end = 0;
        int in = 1;

        int state = end; int wc = 0; int i = 0;

        while (i < str.length())
        {
            if(str.charAt(i) == ' ' || str.charAt(i) == '\n'
                    || str.charAt(i) == '\t')
                state = end;

            else if (state == end)
            {
                state = in;
                ++wc;
            }
            ++i;
        }
        return wc;
    }

    public int countWordsTokenizer(String str){

        return new StringTokenizer(str).countTokens();
    }



}
\$\endgroup\$
  • \$\begingroup\$ The two approaches could not return the same result because StringTokenizer compares chars with \f while countWords method doesn't. \$\endgroup\$ – dariosicily Jul 28 at 18:09
  • 1
    \$\begingroup\$ length of String.split() \$\endgroup\$ – Martin Frank Jul 29 at 5:15
4
\$\begingroup\$

Your approach works well but it doesn't consider punctuation, for example:

@Test
public void testWordCount(){
    String str = "Hey — how are you?";

    assertEquals(4, wc.countWords(str)); // Fail
    assertEquals(4, wc.countWordsTokenizer(str)); // Fail
}

Also StringTokenizer is discouraged for new code. From the Javadoc:

StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead.

The split method accepts a regex as input. This is to split by non-words characters:

public int countWordsSplit(String str) {
    if (str == null || str.isEmpty()) {
        return 0;
    }
    return str.split("\\W+").length;
}

Test:

@Test
public void testWordCount(){
    String str = "Hey — how are you?\t\n";

    assertEquals(4, wc.countWords(str)); // Fail
    assertEquals(4, wc.countWordsTokenizer(str)); // Fail
    assertEquals(4, wc.countWordsSplit(str)); // Pass
}

Edge cases

wc.countWordsSplit("Bill's house"); // returns 3

wc.countWordsSplit("1 2"); // returns 2
wc.countWords("1 2"); // returns 2
wc.countWordsTokenizer("1 2"); // returns 2

// Letters with accents, umlaut etc.
// Other edge cases...

Considerations

It all depends on how you define what a "word" is in the context of your program.

Define what a word is by making some assumptions. Then find a solution that satisfies your requirements. This approach prevents you from having an over-complicated solution that your program doesn't need.

More info here

Many thanks to @RolandIllig for the helpful comments.

| improve this answer | |
\$\endgroup\$
  • 2
    \$\begingroup\$ The lonely comma is unrealistic. A string like hey — how are you? would be more realistic. \$\endgroup\$ – Roland Illig Jul 29 at 7:11
  • \$\begingroup\$ @RolandIllig good point, updated. \$\endgroup\$ – marc Jul 29 at 8:10
  • 1
    \$\begingroup\$ countWordsSplit("") — counting the words should rather be done by findAll("\\w+"). Unfortunately, Java's String class does not offer such a method. \$\endgroup\$ – Roland Illig Jul 30 at 14:22
  • \$\begingroup\$ @RolandIllig thanks again! Added input validation. \$\endgroup\$ – marc Aug 1 at 4:57
  • 1
    \$\begingroup\$ Still not perfect. Some more test cases are "", "\t", "word", " 1 2 ". \$\endgroup\$ – Roland Illig Aug 1 at 11:21
0
\$\begingroup\$

Having a single test case is by far not enough. Here are some more test cases that you should try.

""
"\t"
"word"
" word "
"don't do that"
"what about hyphen-ated words?"
"and these — how many are these?"
"naïve"
"naïve"
"😀😀😀"

If your code handles all of these correctly (that is: exactly as you would expect), it's good.

You should declare the method countWords as public static int. The static keyword means that you don't have to create an unnecessary WordCount object just to call this method. Instead, you can call that method as WordCount.countWords("word").

In your countWords method, you have a bit of code duplication:

str.charAt(i) == ' ' || str.charAt(i) == '\n' || str.charAt(i) == '\t'

In the above code, the expression str.charAt(i) appears 3 times. The code becomes easier understandable if you create a separate method for this purpose:

private static boolean isSpace(char c) {
    return c == ' ' || c == '\n' || c == '\t';
}

And since it is a common task to test whether a character is a space, there's a predefined method for this. It is called Character.isWhitespace. You should probably use that instead of defining your own.

By the way, it is not easy to define what exactly a "word" is and how to split a text into words. Similar to defining what a "character" is. If you want to dig deeper into this topic, have a look at Unicode Text Segmentation, which contains way more details and edge cases than you ever wanted to know.

| improve this answer | |
\$\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.