This commit is contained in:
226
content/courses/prog-intro/homeworks/sum/SumHex/_index.md
Normal file
226
content/courses/prog-intro/homeworks/sum/SumHex/_index.md
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: SumHex
|
||||
weight: 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)
|
||||
|
||||
```java
|
||||
// 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 `main` method almost unchanged. The only modifications to `main` would be changing the data type of `sum`.
|
||||
|
||||
Now `parseNumber` thinks that every number is decimal, but we don't know what to do with hexadecimal numbers. Let's try that
|
||||
|
||||
```bash
|
||||
$ 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.
|
||||
|
||||
```java
|
||||
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
|
||||
|
||||
```java
|
||||
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)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#startsWith-java.lang.String-) that returns `true` if the string starts with `prefix` and `false` otherwise.
|
||||
|
||||
So we can change our code using this information.
|
||||
|
||||
```java
|
||||
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()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-int-) 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.
|
||||
|
||||
```java
|
||||
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)`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-) method to cut off first two symbols (`"0x"`/`"0X"`) in order for `parseInt` to parse number correctly and not give us exceptions. In our case `beginIndex = 2` because 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 `int` data type. Let's look at this example
|
||||
> ```bash
|
||||
> $ 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 `long` data type for storing hexademical values.
|
||||
|
||||
Here's the updated code
|
||||
|
||||
```java
|
||||
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
|
||||
|
||||
```java
|
||||
// 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 `main` method at all, because we have a separate function handling parsing. That's a good sign because our `main` method doesn't know what number parsing algorithm is used and therefore not dependent on it.
|
||||
Reference in New Issue
Block a user