7.3 KiB
title, weight
| title | weight |
|---|---|
| SumHex | 6 |
Task
- The input is in decimal and hexadecimal numbers
- Hexademical numbers has the prefix
0x - The input is case insensitive
- Class should be named
SumHex
Solution
This modification doesn't seem too complicated either.
Let's modify our initial Sum.java and apply all our improvements from a previous modification. (A good exercise would be to try doing that yourself and then comeback to check)
// SumHex.java
public class SumHex {
public static void main(String[] args) {
int sum = 0;
for (String argument : args) {
StringBuilder number = new StringBuilder();
for (char c : argument.toCharArray()) {
if (!Character.isWhitespace(c)) {
number.append(c);
} else {
sum += parseNumber(number);
number = new StringBuilder();
}
}
sum += parseNumber(number);
}
System.out.println(sum);
}
static int parseNumber(StringBuilder number) {
return number.isEmpty() ? 0 : Integer.parseInt(number.toString());
}
}
Note
The importance of these changes, is that in all further modifications we will only be changing our parseNumber method and leaving
mainmethod almost unchanged. The only modifications tomainwould be changing the data type ofsum.
Now parseNumber thinks that every number is decimal, but we don't know what to do with hexadecimal numbers. Let's try that
$ java SumHex 0x1 0x2 0x3
Exception in thread "main" java.lang.NumberFormatException: For input string: "0x1"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at java.base/java.lang.Integer.parseInt(Integer.java:778)
at SumHex.parseNumber(SumHex.java:25)
at SumHex.main(SumHex.java:18)
So we need to split decimal and hexadecimal processing inside parseNumber. Our code would look something like this.
static int parseNumber(StringBuilder number) {
if (!number.isEmpty()) {
if (/* number is hexadecimal */) {
return /* parse hexadecimal */;
} else { // if the number if decimal
return Integer.parseInt(number.toString());
}
}
return 0;
}
Here we have 2 major questions:
- How to check if the number is hexadecimal?
- How to parse the hexadecimal number?
We can answer the 1st question, because we know that hexadecimal number starts with "0x". And we know that the input is not case sensitive, so "0X" is also a valid option.
So we can change our pseude-code like this
static int parseNumber(StringBuilder number) {
if (!number.isEmpty()) {
if (/* number starts with "0x" or "0X" */) {
return /* parse hexadecimal */;
} else { // if the number is decimal
return Integer.parseInt(number.toString());
}
}
return 0;
}
But how to check if the string starts with some prefix? There is a String.startsWith(String prefix) that returns true if the string starts with prefix and false otherwise.
So we can change our code using this information.
static int parseNumber(StringBuilder number) {
if (!number.isEmpty()) {
String numberString = number.toString();
if (
numberString.startsWith("0x") ||
numberString.startsWith("0X")
) {
return /* parse hexadecimal */;
} else { // if the number is decimal
return Integer.parseInt(number.toString());
}
}
return 0;
}
Note
||here is a logical or.
We still have one problem. How to parse the hexadecimal number?
Using Integer.parseInt() method. It turnes out, that it has the second argument radix where we can specify the base of the numerical system (in our case it's 16).
So our code will look like this.
static int parseNumber(StringBuilder number) {
if (number.isEmpty()) {
String numberString = number.toString();
if (
numberString.startsWith("0x") ||
numberString.startsWith("0X")
) {
return Integer.parseInt(numberString.substring(2), 16);
} else {
return Integer.parseInt(numberString);
}
}
return 0;
}
Note
Here we used
String.substring(int beginIndex)method to cut off first two symbols ("0x"/"0X") in order forparseIntto parse number correctly and not give us exceptions. In our casebeginIndex = 2because it's the first index of the substring that we want to get. Remember that indexing starts with 0 (not 1).
Important
It's important to know that we can't store all hexadecimal numbers in
intdata type. Let's look at this example$ java SumHex 0x80000000 Exception in thread "main" java.lang.NumberFormatException: For input string: "80000000" under radix 16 at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:662) at SumHex.parseNumber(SumHex.java:30) at SumHex.main(SumHex.java:18)That's why we will need to use
longdata type for storing hexademical values.
Here's the updated code
static long parseNumber(StringBuilder number) { // int -> long
if (!number.isEmpty()) {
String numberString = number.toString();
if (numberString.startsWith("0x") || numberString.startsWith("0X")) {
// Integer.parseInt -> Long.parseLong
return Long.parseLong(numberString.substring(2), 16);
} else {
return Integer.parseInt(numberString);
}
} else {
return 0;
}
}
Let's present the full solution
// SumHex.java
public class SumHex {
public static void main(String[] args) {
int sum = 0;
for (String argument : args) {
StringBuilder number = new StringBuilder();
for (char c : argument.toCharArray()) {
if (!Character.isWhitespace(c)) {
number.append(c);
} else {
sum += parseNumber(number);
number = new StringBuilder();
}
}
sum += parseNumber(number);
}
System.out.println(sum);
}
static long parseNumber(StringBuilder number) {
if (!number.isEmpty()) {
String numberString = number.toString();
if (numberString.startsWith("0x") || numberString.startsWith("0X")) {
return Long.parseLong(numberString.substring(2), 16);
} else {
return Integer.parseInt(numberString);
}
} else {
return 0;
}
}
}
Important
Notice, that we didn't change
mainmethod at all, because we have a separate function handling parsing. That's a good sign because ourmainmethod doesn't know what number parsing algorithm is used and therefore not dependent on it.