Count number of commas within a string except for commas between double quotes

I have the following function to count the number of commas (or any other character) in a String without counting those inside double quotes. I want to know if there's a better way to achieve this or even if you can find some case where this function can crash.

public int countCharOfString(char c, String s) {
    int numberOfC = 0;
    boolean doubleQuotesFound = false;
    for(int i = 0; i < s.length(); i++){
        if(s.charAt(i) == c && !doubleQuotesFound){
            numberOfC++;
        }else if(s.charAt(i) == c && doubleQuotesFound){
            continue;
        }else if(s.charAt(i) == '\"'){
            doubleQuotesFound = !doubleQuotesFound;
        }
    }
    return numberOfC;
}

Thanks for any advise

Answers


This implementation has two differences:

  • Use CharSequence instead of String
  • No need of a boolean value to track if we are inside a quoted subsequence.

The function:

public static int countCharOfString(char quote, CharSequence sequence) {

    int total = 0, length = sequence.length();

    for(int i = 0; i < length; i++){
        char c = sequence.charAt(i);
        if (c == '"') {
            // Skip quoted sequence
            for (i++; i < length && sequence.charAt(i)!='"'; i++) {}
        } else if (c == quote) {
            total++;
        }
    }

    return total;
 }

public static int countCharOfString(char c, String s)
{
    int numberOfC = 0;
    int innerC = 0;
    boolean holdDoubleQuotes = false;
    for(int i = 0; i < s.length(); i++)
    {
        char r = s.charAt(i);
        if(i == s.length() - 1 && r != '\"')
        {
            numberOfC += innerC;
            if(r == c) numberOfC++;
        }
        else if(r == c && !holdDoubleQuotes) numberOfC++;
        else if(r == c && holdDoubleQuotes) innerC++;
        else if(r == '\"' && holdDoubleQuotes)
        {
            holdDoubleQuotes = false;
            innerC = 0;
        }
        else if(r == '\"' && !holdDoubleQuotes) holdDoubleQuotes = true;
    }
    return numberOfC;
}

System.out.println(countCharOfString(',', "Hello, BRabbit27, how\",,,\" are, you?"));

OUTPUT:

3

An alternative would be using regex:

public static int countCharOfString(char c, String s)
{
   s = " " + s + " "; // To make the first and last commas to be counted
   return s.split("[^\"" + c + "*\"][" + c + "]").length - 1;
}

  • you should not call charAt() several times inside the loop. Use a char variable.
  • you should not call length() for each iteration. use an int before the loop.
  • you should avoid duplicate comparison with c - use nested if/else.

Maybe not the fastest...

public int countCharOfString(char c, String s) {
    final String removedQuoted = s.replaceAll("\".*?\"", "");
    int total = 0;
    for(int i = 0; i < removedQuoted.length(); ++i)
        if(removedQuoted.charAt(i) == c)
            ++total;
    return total;
}

Simpler, less bug-prone (and yes, less performant than walking the string char by char and keeping track of everything by hand):

public static int countCharOfString(char c, String s) {
  s = s.replaceAll("\".*?\"", "");
  int cnt = 0;
  for (int foundAt = s.indexOf(c); foundAt > -1; foundAt = s.indexOf(c, foundAt+1)) 
    cnt++;
  return cnt;
}

It takes a large string to make a big difference.

The reason this code is faster is it contains on average 1.5 checks per loop instead of 3 checks per loop. It does this by using two loops, one for quoted and one for unquoted state.

public static void main(String... args) {
    String s = generateString(20 * 1024 * 1024);
    for (int i = 0; i < 15; i++) {
        long start = System.nanoTime();
        countCharOfString(',', s);
        long mid = System.nanoTime();
        countCharOfString2(',', s);
        long end = System.nanoTime();
        System.out.printf("countCharOfString() took %.3f ms, countCharOfString2() took %.3f ms%n",
                (mid - start) / 1e6, (end - mid) / 1e6);
    }
}

private static String generateString(int length) {
    StringBuilder sb = new StringBuilder(length);
    Random rand = new Random(1);
    while (sb.length() < length)
        sb.append((char) (rand.nextInt(96) + 32)); // includes , and "
    return sb.toString();
}

public static int countCharOfString2(char c, String s) {
    int numberOfC = 0, i = 0;
    while (i < s.length()) {
        // not quoted
        while (i < s.length()) {
            char ch = s.charAt(i++);
            if (ch == c)
                numberOfC++;
            else if (ch == '"')
                break;
        }
        // quoted
        while (i < s.length()) {
            char ch = s.charAt(i++);
            if (ch == '"')
                break;
        }
    }
    return numberOfC;
}


public static int countCharOfString(char c, String s) {
    int numberOfC = 0;
    boolean doubleQuotesFound = false;
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == c && !doubleQuotesFound) {
            numberOfC++;
        } else if (s.charAt(i) == c && doubleQuotesFound) {
            continue;
        } else if (s.charAt(i) == '\"') {
            doubleQuotesFound = !doubleQuotesFound;
        }
    }
    return numberOfC;
}

prints

countCharOfString() took 33.348 ms, countCharOfString2() took 31.381 ms
countCharOfString() took 28.265 ms, countCharOfString2() took 25.801 ms
countCharOfString() took 28.142 ms, countCharOfString2() took 14.576 ms
countCharOfString() took 28.372 ms, countCharOfString2() took 14.540 ms
countCharOfString() took 28.191 ms, countCharOfString2() took 14.616 ms

You could also use a regex and String.split()

It might look something like this:

public int countNonQuotedOccurrences(String inputstring, char searchChar)
{
  String regexPattern = "[^\"]" + searchChar + "[^\"]";
  return inputString.split(regexPattern).length - 1;
}

Disclaimer:

This just shows the basic approach.

The above code will not check for searchChar at the beginning or end of the string.

You could either check for this manually or add to regexPattern.


Need Your Help

How to manage CheckBox in XAML?

c# wpf xaml checkbox syncfusion

I am a beginner in programming. I want to manage my Checkbox by including "if-else-condition" in it. For examples, there are two columns in my grid which are named "readable" and "writable" (public...

Facebook redirect with extended permissions

facebook

I am trying to work out facebook connect based on this tutorial: