--- title: Sum weight: 5 --- # Task 1. You need to create a `Sum` class which will sum integers from command line arguments and output the sum to console. 2. Examples: ```sh java Sum 1 2 3 ``` Expected output: `6`. ```sh java Sum 1 2 -3 ``` Expected output: `0`. ```sh java Sum "1 2 3" ``` Expected output: `6`. ```sh java Sum "1 2" " 3" ``` Expected output: `6`. ```sh java Sum " " ``` Expected output: `0`. 3. Arguments can be: - digits, - signes `+` and `-`, - [space symbols](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#isWhitespace(char)) 4. You can assume that `int` type is sufficient for in-between calculations and result. 5. Before doing the task make sure to read docs for classes [`String`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html) and [`Integer`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Integer.html). 6. For debugging use [`System.err`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#err), because it will be ingnored by the testing program. --- ## Solution After reading about [`String`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/String.html), [`Integer`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Integer.html), [`System.err`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#err) we now know about some usefull methods: - [`Integer.parseInt(String s, int radix)`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-) which parses the string argument as a signed integer in the radix specified by the second argument. So it basically converts a number from the `String` data type to `int`. - [`Character.isWhitespace(char ch)`](https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isWhitespace-char-) which checks if a character `ch` is a space symbol or not. Now let's start coding. Firstly let's define the structure of our program. I will be putting the name of the file and its path (if it's nessesary) at the first line comment. ```java // Sum.java public class Sum { public static void main(String[] args) { // ... } } ``` Okay that's done. What do we do next? Let's look at `String[] args` argument to our `main` method. It represents an array of command line arguments which we need to sum. So now we made our task a little bit easier. Now we can just say that we need to find sum of elements of array `args`. How are we going to do it though? First let's understand what we can do with this array. Let's modify our class a little bit. ```java // Sum.java public class Sum { public static void main(String[] args) { System.out.println(args.length); } } ``` We've added `System.out.println(args.length)` which takes the field `length` from our `args` object and prints it to the console. Let's try it. First compile our class using ```sh $ javac Sum.java ``` And then we can do some manual testing. ```sh $ java Sum 1 2 3 3 ``` We got `3` as an output as expected. We gave our program 3 command line arguments: `1`, `2` and `3`. Here are all of the examples ```sh $ java Sum 1 2 -3 3 ``` ```sh $ java Sum "1 2 3" 1 ``` > [!NOTE] > Notice, that we got 1 instead of 3. That's because we put our arguments in `""` so this becomes a single string argument. ```sh $ java Sum "1 2" " 3" 2 ``` ```sh $ java Sum " " 1 ``` > [!NOTE] > Here program gives us 1 instead of 0, because despite not having any numbers in the arguments a single whitespace is still an argument. Now let's try not only to count our arguments but to list them as well. Let's modify our program a little bit more. ```java // Sum.java public class Sum { public static void main(String[] args) { System.out.println("number of arguments: " + args.length); for (String argument : args) { System.out.println(argument); } } } ``` Here I used `for` loop to do printing ***for*** every `String` element in `args`. Let's try this with our examples. And don't forget to recompile using `javac Sum.java`. ```sh $ java Sum 1 2 3 number of arguments: 3 1 2 3 ``` ```sh $ java Sum 1 2 -3 number of arguments: 3 1 2 -3 ``` ```sh $ java Sum "1 2 3" number of arguments: 1 1 2 3 ``` > [!NOTE] > Again, notice only ***one*** string argument. ```sh $ java Sum "1 2" " 3" number of arguments: 2 1 2 3 ``` ```sh $ java Sum " " number of arguments: 1 ``` Okay, now that we know how to ***iterate*** (or do something for every element), we can calculate the *sum* of all the numbers in the `args` array. To do that we need to create a new variable `sum` of *integer* type and assign it the **value** of `0`. Then for every number in `args` we will add it to `sum`, and so by the end of array we will have the sum of all numbers stored in variable `sum`. This is what the code will look like ```java // Sum.java public class Sum { public static void main(String[] args) { System.out.println("number of arguments: " + args.length); int sum = 0; for (String argument : args) { sum = sum + argument; System.out.println(argument); System.out.println(sum); } System.out.println(sum); } } ``` Seems ok. But let's test it. ```sh $ javac Sum.java Sum.java:9: error: incompatible types: String cannot be converted to int sum = sum + argument; ^ 1 error ``` We got a compilation error that says `String cannot be converted to int`. It means that when we try to add 2 variables `sum` and `argument` their types don't match. The type of `sum` is integer and string for `argument`. It seems pretty logical because what do we expect when adding for example `1` and `apple`?.. > [!IMPORTANT] > So we need to *cast* `argument` to integer data type so that we can add it to `sum`. We can do so using [`Integer.parseInt()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html#parseInt-java.lang.String-) method. It takes a `String s` and returns an `int`, which a string `s` represents. For example this code will work as expected ```java // ParseIntExample.java public class ParseIntExample { public static void main(String[] args) { String s = "123"; int sum = 321; //! int result = s + sum; } } ``` The commented out line (`//!`) would have given us an exception. ```bash $ javac ParseIntExample.java ParseIntExample.java:8: error: incompatible types: String cannot be converted to int int result = s + sum; ^ 1 error ``` > [!IMPORTANT] > Please note that if we change `result` data type to `String`, the code will work just fine. > ```java > // ParseIntExample.java > public class ParseIntExample { > > public static void main(String[] args) { > String s = "123"; > int sum = 321; > String result = s + sum; > System.out.println(result); > } > } > ``` > ```bash > $ javac ParseIntExample.java && java ParseIntExample > 123321 > ``` > In this case `sum` will be ***automatically casted*** to `String` and two strings will just concatenate. So we figured out that we need to cast `argument` to integer type. Let's change our code and test it. ```java // Sum.java public class Sum { public static void main(String[] args) { System.out.println("number of arguments: " + args.length); int sum = 0; for (String argument : args) { sum = sum + Integer.parseInt(argument); System.out.println( "current argument: " + argument + ", current sum: " + sum ); } System.out.println("final sum: " + sum); } } ``` ```bash $ java Sum 1 2 3 number of arguments: 3 current argument: 1, current sum: 1 current argument: 2, current sum: 3 current argument: 3, current sum: 6 final sum: 6 ``` ```bash $ java Sum 1 2 -3 number of arguments: 3 current argument: 1, current sum: 1 current argument: 2, current sum: 3 current argument: -3, current sum: 0 final sum: 0 ``` ```bash $ java Sum "1 2 3" number of arguments: 1 Exception in thread "main" java.lang.NumberFormatException: For input string: "1 2 3" 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 Sum.main(Sum.java:9) ``` As we can see because we have only 1 argument (`"1 2 3"`) we try to parse it as an integer but `"1 2 3"` contains empty spaces, so it can't be parsed as an integer. We need to manually extract numeric values from the `argument` string. In order to do that we will iterate over each symbol of the `argument` string and extract numeric values. We will have another variable `number` of `String` type in which we will be storing extracted numeric values. For every character or symbol in the string, we will check it to be an empty space or a digit. If it is a digit, we will add/concatenate with `number`, and if it is an empty space, we will add the numeric value of `number` to `sum` and then assign empty string to `number` because we reached the end of the current number and are ready to move to the next one. > [!IMPORTANT] > It is important to add numeric value of `number` (if it's not empty) to `sum` after we iterated over the entire string because if it ends like this `"1 2 3"`, we don't nessesarily have an empty space at the end so `"3"` (in this case) will be stored in `number` but NOT added to `sum`. Let's implement those ideas into solution. We will get rid of all the `println` messages so it's not drawing our attention. > [!NOTE] > Also I want to point out that we can't just iterate over `String`. First we need to turn it into an ***array of characters*** using [`String.toCharArray()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#toCharArray--) method. > [!NOTE] > We will be checking for an empty space using [`Character.isWhitespace(char ch)`](https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#isWhitespace-char-) which outputs `true` or `false` accordingly. ```java // Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { String number = ""; // we will form numbers here for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number = number + c; // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number); // add number to sum } number = ""; // empty the number } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number); // add number to sum } } System.out.println(sum); } } ``` ```bash $ java Sum 1 2 3 6 ``` ```bash $ java Sum 1 2 -3 0 ``` ```bash $ java Sum "1 2 3" 6 ``` ```bash $ java Sum "1 2" " 3" 6 ``` ```bash $ java Sum " " 0 ``` This code works and passes all the tests! But can we improve it? One efficiency improvement we can make is to get rid of concatenating strings. You can read why it's bad [here](https://stackoverflow.com/questions/18561424/using-for-strings-in-a-loop-is-it-bad-practice) or [here](https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://www.reddit.com/r/javahelp/comments/19e9jic/question_about_dont_concat_string_in_a_loop/&ved=2ahUKEwiCpva0y-GTAxUeB9sEHbvzHj4QFnoECCAQAQ&usg=AOvVaw3gk39Xk7Jz9_PmKVIO81nK). We can use [StringBuilder](https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) because it can ***build*** strings more efficiently. So here's the updated code ```java // Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); // we will form numbers here using StringBuilder for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number.append(c); // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number.toString()); // add number to sum } number = new StringBuilder(); // empty the number by creating new empty StringBuilder } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum = sum + Integer.parseInt(number.toString()); // add number to sum } } System.out.println(sum); } } ``` > [!IMPORTANT] > Note, that we can't just do `Integer.parseInt(number)`, because `number` is an instance of `StringBuilder`, not just a regular `String`. In order to get the current state of the string we can use [`StringBuilder.toString()`](https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html#toString--) method. > [!NOTE] > Also it's worth mentioning, that we can shorten operations like thse > ```java > arg1 = arg1 X arg2 > ``` > to > ```java > arg1 X= arg2 > ``` > where `X` is some operation like `+`, `-`, `*`, `/` and so on. Let's make this change to our code ```java // Sum.java public class Sum { public static void main(String[] args) { int sum = 0; for (String argument : args) { StringBuilder number = new StringBuilder(); // we will form numbers here using StringBuilder for (char c : argument.toCharArray()) { if (!Character.isWhitespace(c)) { // if character is not whitespace number.append(c); // concatenate new digit } else { // if character is whitespace if (!number.isEmpty()) { // if number is not empty sum += Integer.parseInt(number.toString()); // add number to sum } number = new StringBuilder(); // empty the number by creating new empty StringBuilder } } // check for any remaining digits in number if (!number.isEmpty()) { // if number is not empty sum += Integer.parseInt(number.toString()); // add number to sum } } System.out.println(sum); } } ``` This is it! The code is ready.