The problem is stated as follows:
Given a string, return the sum of the numbers appearing in the string, ignoring all other characters. A number is a series of 1 or more digit chars in a row. (Note: Character.isDigit(char) tests if a char is one of the chars '0', '1', .. '9'. Integer.parseInt(string) converts a string to an int.)
sumNumbers("abc123xyz") → 123
sumNumbers("aa11b33") → 44
sumNumbers("7 11") → 18
Here's the program I wrote based on Character.isDigit(char)
and Integer.parseInt(string)
.
public int sumNumbers(String str) {
int sum = 0;
for (int i = 0; i < str.length(); i++) {
if (Character.isDigit(str.charAt(i))) {
int count = 0;
for (int j = i; j < str.length(); j++) {
if (Character.isDigit(str.charAt(j))) count++;
else break;
}
sum += Integer.parseInt(str.substring(i, i + count));
i += count;
}
}
return sum;
}
I'm not sure if I should make it more modular since it's pretty straightforward. I was wondering if I could do this without count
. Also, I know it's nit-picky, but I want to know if the way I format my for loops and if else statements is good style. This is just a standalone program, so I'm not really concerned about the public/private issue.
3 Answers 3
I want to know if the way I format my for loops and if else statements is good style.
On the whole, yeah, looks good.
I recommend you use braces for one-liner blocks as well. In this instance, I'm not batting an eye at it—it's fine—but it's a good habit to learn.
I was wondering if I could do this without
count
.
Yes, since count = j - i
, you can elide it.
int sumNumbers(String str) {
int sum = 0;
for (int i = 0; i < str.length(); i++) {
if (Character.isDigit(str.charAt(i))) {
int j;
// start at i+1 because we know i has a digit
for (j = i + 1; j < str.length(); j++) {
if (!Character.isDigit(str.charAt(j))) {
break;
}
}
sum += Integer.parseInt(str.substring(i, j));
i = j;
}
}
return sum;
}
Alternatively, if the break
is a bit of an eye-sore, you could hoist the check into the for-header, but I'm not 100% it's an improvement:
int sumNumbers(String str) {
int sum = 0;
for (int i = 0; i < str.length(); i++) {
if (Character.isDigit(str.charAt(i))) {
int j;
// start at i+1 because we know i has a digit
for (j = i + 1; j < str.length() && Character.isDigit(str.charAt(j)); j++);
sum += Integer.parseInt(str.substring(i, j));
i = j;
}
}
return sum;
}
Note: String.substring
creates a new string since Java 7—in the reference implementation, at least, and String.toCharArray
creates a new object too. If you want to avoid these allocations, you can do the char-to-digit conversion manually, but it's a bit messier, easy to muck up, and I feel that's out of scope for the exercise.
-
\$\begingroup\$ I like you're answer the best because it answered my individual questions even though I think Oscar Smith had a great answer as well. \$\endgroup\$dirtysocks45– dirtysocks452017年10月29日 09:29:21 +00:00Commented Oct 29, 2017 at 9:29
-
\$\begingroup\$ Also, the boolean condition in the second for loop is more of an eye sore to me. I find nothing wrong with
break
. \$\endgroup\$dirtysocks45– dirtysocks452017年10月29日 09:30:10 +00:00Commented Oct 29, 2017 at 9:30
There are a couple things you can do here to make things simpler. The first is to make your inner loop a while loop instead of a for loop. With this idea, you can build up each number char by char, using Character.getNumericValue()
which will return 1
for '1'
etc. With charVal, we can incrementally build up our number by multiplying the by 10 and adding the new digit. When we reach a non-digit, we add the current num to our sum, and set sum to 0. Since the code now only operates one char at a time, we don't need i
, or j
either. With these changes, your updated code is
public int sumNumbers(String str) {
int sum = 0;
int num = 0;
for (char ch : exampleString.toCharArray()) {
int digit = Character.getNumericValue(ch);
if (digit >= 0 && digit <= 9) {
num = num * 10 + digit;
} else {
sum += num;
num = 0;
}
}
return sum + num;
}
-
\$\begingroup\$ How come you don’t initialize num to 1? And I think digit would be a better name. \$\endgroup\$dirtysocks45– dirtysocks452017年10月28日 18:49:09 +00:00Commented Oct 28, 2017 at 18:49
-
1\$\begingroup\$ You initialize
num
to 0, because the first time you see a5
(eg), you wantnum*10 + 5
to equal 5, which means num needs to start at 0. It also means that when you don't hit number characters, you can addnum
tosum
without changing the answer. \$\endgroup\$Oscar Smith– Oscar Smith2017年10月28日 18:52:14 +00:00Commented Oct 28, 2017 at 18:52 -
\$\begingroup\$ You also have some typos like missing
;
's and and an extra)
but when I started testing your program, I get different results. Some tests are now failing.sumNumbers("abc123xyz")
returns0
instead of123
. \$\endgroup\$dirtysocks45– dirtysocks452017年10月28日 19:00:00 +00:00Commented Oct 28, 2017 at 19:00 -
\$\begingroup\$ I edited your code to fix the above test case but cases where the integer is the last value in the
String
such assumNumbers("7 11")
are returning the first integers like7
but forgetting about the last one like11
. \$\endgroup\$dirtysocks45– dirtysocks452017年10月28日 19:16:26 +00:00Commented Oct 28, 2017 at 19:16 -
1\$\begingroup\$ I just fixed the
sumNumbers("7 11")
case \$\endgroup\$Oscar Smith– Oscar Smith2017年10月28日 19:21:45 +00:00Commented Oct 28, 2017 at 19:21
Your code looks fine and is short enough to be understood.
If you don't want to work on the level of individual characters, you can use regular expressions. Here is the code:
public static int sumNumbers(String str) {
Matcher m = Pattern.compile("[0-9]+").matcher(str);
int sum = 0;
while (m.find()) {
sum += Integer.parseInt(m.group());
}
return sum;
}
This is not the fastest code, but I find it easy to read once you know that [0-9]
means a digit, and the +
means 1 or more times.