Counting Characters

Started by Atelier, Sat 20/06/2009 18:01:17

Previous topic - Next topic

Atelier

Hullo.

I have a text box on a GUI. When the user clicks the enter button after they have entered their name, is there any way to count how many of a certain letter their input has? For example, how would I count how many Ls appear in their name and store that as an int using a global variable?

Thanks in advance.

GuyAwesome

#1
Not directly, but it should be easy enough to do. Something like
Code: ags

function Count(this String*, String toCount) {
  int Count, Pos;
  while (Pos < this.Length) {
    if (this.Chars[Pos] == toCount.Chars[0]) Count ++; // Probably a simpler way to do this, but it escapes me ATM
    Pos ++;
  }
  return Count;
}

Then you could do
Code: ags

String UserInput = InputBox("Type a word");
Display("%s contains 'L' %d times", UserInput,  UserInput.Count("L"));

(You don't have to go the extender function method, of course. I just think it's neater if you're going to be doing this a few times... Since it doesn't look like you will be, the importat part is the while loop, just replace this with the name of the String you want to check and toCount.Chars[0] with the character to check for, e.g. 'L' - including the ' '.)

Possible downside is, it's case-sensitive ("hell" contains no 'L's, "HELL" contains 2). You could get around this by converting the input and toCount Strings to lower-case in the function (String.LowerCase).

EDIT, after monkey_05_06:
The caseSensitive parameter, you're totally right - I tested that version of the code, and I would've included it but it didn't seem particularly necessary (as it's probably only going to be used one time...). The char/String thing was deliberate - I personally prefer "L" to 'L', and the question was only about counting letters, not strings - but you're right (again), to make it as generic as possible, your naming is the best. The name - again, less generic, but is that really needed in this case?
(Off topic: I don't know, I can't win - make it generic, people accuse me of over complicating things, make it less generic, and I get accused of not being specific enough... :P)

monkey0506

#2
There's not really a "simpler way," but I do see some "problems"* with your code Guy. The very first item is the ambiguity of the function's name. Something like CountOfChar might give a better description of what the function is doing. There's nothing worse for someone learning to script than constantly having to keep looking up the same functions just because they can't discern from the name what the function does.

The next problem is that you pass a parameter of String type, yet you only use the first character of it. This implies to the user, when they see the calltip/parameter list, that your function is going to count how many times a certain substring appears within the string. The ambiguity of the function name (as noted) does nothing to support or suppress this. Thusly, the function may be extremely counter-intuitive to the end-user.

That aside, the final problem I really see is the one you already acknowledged yourself about the case-sensitivity. That's something that would be very easy to fix, and even make an optional parameter! ;)

I'm not trying to grade you on your code or anything, but certainly you must agree that making it clear what you're doing and providing the most flexibility are desirable.

So, you could do this with the above code:

Code: ags
// Script.ash - script header
///Returns the number of instances of a specific character within the String.
import function CountOfChar(this String*, char theChar, bool caseSensitive=true);

// Script.asc - script body
function CountOfChar(this String*, char theChar, bool caseSensitive) {
  String buffer = this; // make a copy of "this" in case we have to change its case
  if (!caseSensitive) { // case sensitivity is OFF
    // make both lowercase so the case will match (i.e., the case is ignored in our comparison)
    buffer = buffer.LowerCase();
    if ((theChar >= 65) && (theChar <= 90)) { // theChar is an uppercase letter
      theChar += 32; // offsetting the ASCII value by 32 now makes it lowercase
    }
  }
  int count = 0;
  int pos = 0;
  while (pos < buffer.Length) {
    if (buffer.Chars[pos] == theChar) count++;
    pos++;
  }
  return count;
}


The import in the script header of course makes the function available to all of our (subsequent) scripts, and also allows us to specify a default setting for caseSensitive so the parameter is optional. One other benefit is that as of AGS 3.1 (I believe, could be wrong on the version here) by adding the comment with 3-slashes prior to the import, we also get calltip helper text, so the user won't have to look up the function in our documentation every time they call it. That's something I find quite useful that's very simple to implement. Something I find particularly useful for public releases is to prefix these helper texts with the name of the module so the end-user will also know where the code is coming from.

Your example code would remain very similar though:

Code: ags
String UserInput = InputBox("Type a word");
Display("%s contains 'L' %d times", UserInput, UserInput.CountOfChar('L'));


Then if the user types in "HELL" it would display: "HELL contains 'L' 2 times." If the user types "hell" it would display: "hell contains 'L' 0 times." If you want to make sure it counts the letter L in both cases, rather than only where it appears capitalized, just change the Display line to:

Code: ags
Display("%s contains 'L' %d times", UserInput, UserInput.CountOfChar('L', false));


Other than that I'd say this is the best way to achieve what Atelier was looking for. :=

*NOTE: There is nothing functionally wrong with Guy's code. I am simply pointing out some methods here to make 1) what the code is doing clearer to the user and 2) increase the functionality of the code itself.

SMF spam blocked by CleanTalk