Tuesday, July 8, 2025

Switch

 Other than if/if-else/if-else if-else and the ternary operator, there is yet another common and important conditional expression in Java that every good programmer should be aware of: the switch statement.

switch looks like this

int day = 4;

switch (day) {

    case 1:

        System.out.println("Monday");

        break;

    case 2:

        System.out.println("Tuesday");

        break;

    case 3:

        System.out.println("Wednesday");

        break;

    case 4:

        System.out.println("Thursday");

        break;

    case 5:

        System.out.println("Friday");

        break;

    case 6:

        System.out.println("Saturday");

        break;

    case 7:

        System.out.println("Sunday");

        break;

    default:

        System.out.println("Invalid day");

}

A few things are important to notice:

  • You work on the different states of a variable (inside the switch parentheses), which must have been declared and initialized before the switch
  • You differentiate each of those states as a “case,” after which you put a colon and the behavior you want
  • You don’t use braces to enclose multiple statements, like you would in an if, else, or loop. You instead use “break” to determine when you’re done giving behavior for a specific case
  • If a “break” is ever omitted, Java will “fall through” and continue executing either until a break, return, or the end of the switch
  • For all other cases besides the ones you explicitly label in this construction, you can define a fallback behavior with the “default” part of the statement. Typically, this appears last, as above, but technically, Java's specifications allow it to be placed in any order among the different possible branches of the switch statement. 
  • Just like you can nest if-statements and loops, you can nest switch statements

You won’t see switch statements as often as you will if statements, but it’s still important to know how to build them, and that every idea expressible by one form is expressible by the other. Happy switching!

Monday, July 7, 2025

The + operator

 The “+” operator has a very special meaning in Java. It can only ever mean one of two things: addition of numbers, or concatenation of Strings.


[A number] + [another number] follows these rules:

·       A byte + a byte, short, char, or int  = an int

·       A byte + a double, long, or float = the non-byte type

·       A char + a char or int = an int

·       A char + a long, float, or double = the non-char type

·       A double + a double = a double

·       A float + a float = a float

·       A float + a double = a double

·       An int + an int = an int

·       An int + a double, float, or long = the non-int type

·       A long + a long = a long

·       A long + a float or double = the non-long type

·       A short + a byte, short, or char = an int

·       A short + a long, float, or double = the non-short type

Let’s suppose we have an int with value 73, and another with value 1234. Then if we “+” them, the result will be the totally expected 1307. The other numeric types work similarly. Just be mindful of the promotion rules for certain results.

Strings are a little bit more complex. Let’s start with a straightforward case: a String “+” a String.

Suppose one String has the value “Hello,” (note the comma is inside the String) and the other has the value “World!” and we assign

String result = s1+ s2;

Then the variable result will contain “Hello, World”—that is, every character in the first String, including leading or trailing spaces, immediately followed by every character in the second String, including leading or trailing spaces.

Now if we mix in numbers, things get more complex, because concatenation and numerical addition happen left to right:

Using “+” with “Hello” and “2” and 345 in the following order:

“Hello” + 2 + “345”

may produce some behavior that at first glance is unexpected.

This will, in fact, produce “Hello2345”—not, as some of you may (reasonably) expect, “Hello347”.

The concatenator operator sees the first operand is “Hello” and the second is a number, so it converts the number to a String, and we get “Hello2”. Then, it sees the String “345” and appends it to “Hello2” to get the final result of “Hello2345”.

Now, if the things to be concatenated were in another order, with slightly different types, the result would be much more in line with what you first expected:

2 + 345 + “Hello” indeed produces “347Hello” because the addition of numbers 2 + 345 is done first, then that number is converted to a String as “347” and then it is appended to “Hello”

Any behavior other than straightforward addition of numbers, or concatenation of strings, is not allowed with +, and, for example, trying to somehow combine a Puppy and a Kitten with + (now, calling Puppy’s toString() method and concatenating that with Kitten’s toString() is a different story) will cause a compilation error. No other behavior for + can ever be defined by the user. 

Sunday, July 6, 2025

A pitfall with shorts

 There is something important to note about bytes and shorts especially—smaller, whole-number types in Java.


Let’s assume, for example, that we’ve assigned the values 30 and 40 into two different shorts, and we want to add them. A short can hold up to 32767 on the positive end (and down to -32768 on the negative end), so clearly, the result of 30+40 is in-bounds for a short, since it’s just 70.

But they undergo a process called “promotion,” which introduces a trick that most programmers might not even see if they don’t really dig into the language (because almost all whole numbers are ints or longs, and almost all decimal numbers are doubles): the language turns the result of 30 + 40 into an integer, so you cannot save the result into a short without explicitly marking it as such via a cast, and omitting the cast will cause an error.

The reason is actually relatively simple: Imagine I have 20000 (that is, twenty thousand) plus 15000 (fifteen thousand). Either of those is perfectly legal as a short. But the fact that each is legal on its own, as we can see, is not a guarantee that the sum or the product will be legal. 20000 + 15000 is 35000, which is bigger than 32767, so it fits in an int, but not in a short. The result is therefore by default placed into an int, since the sum of the biggest short plus the biggest short does not fit into a short, but does into an int.

To go back to our previous (acceptable) example, to avoid this default behavior, you must explicitly cast the result back into a short, or the compiler will complain.

You must write:

short a = 30;
short b = 40;
short res = (short) a + b;


Omitting the (short) cast—telling the compiler to force the result into a short, because it won’t originally be one—and just doing the following will result in an error, as sensible as this may look:

short a = 30;
short b = 40;
short res = a + b;

At this point, a+b is an int, not a short, and int values can be much bigger (more than 2.1 billion, versus about 32 thousand), so you have no guarantee—unless you make the cast, and deal with the consequences---that the result will be in the type you think it is.

The way you cast something in Java—whether you’re casting object-to-object or primitive-to-primitive— is by including the type you want it to become in parentheses, like so:

short res = (short) a + b;

The “(short)” part of this line of code does the casting of the int (remember, a short plus a short is an int)

 

Assignment inheritance hierarchies

Assignment compatibility is a key concept in Java. It may sound daunting at first, but I promise it isn’t. Basically, it comes down to this:

  • Is the thing you want to assign the same type as the thing you want to assign to? Or, if not, then at least
  • Is the thing you want to assign guaranteed to have all the properties of the thing you want to assign to?

We’ve already covered this when we looked at inheritance.

Suppose I have a CollegeStudent class that extends Student.

Then, I can always say

Student s = new CollegeStudent();

because every CollegeStudent is a Student because CollegeStudent extends Student.

CollegeStudent objects, because of this “extends” relationship (thought of as “is a”), have all the properties of a Student, so the assignment of a CollegeStudent object into a Student reference is perfectly legal. There is nothing in a Student that is not also present in a CollegeStudent. A CollegeStudent may have more than just a Student, but it has at least all the properties and behaviors of a Student.  

There, of course, exists a trivial case of this.

You can always say that Object o = new CuteFluffyPony();

because every CuteFluffyPony by definition is an Object because every class implicitly extends Object.

An example of an assignment that would not be legal would be the following:

suppose:

Bird extends Animal
and also that
Cat extends Animal

Bird myBird =  new Cat();

Bird objects and Cat objects are both Animal objects, but Birds and Cats do not have a relationship like Students and CollegeStudents do. Birds and Cats are, at best, siblings, so there is no guarantee  that all the promises of a Bird are fulfilled in a Cat, so this assignment (or the reverse, Cat myCat = new Bird();) are always illegal.

In other words, an assignment is legal if:

  • The left and right object types are the same or, if not
  • The right object type is a sibling of the left object type, such that the right and left objects (in that direction) are perfectly interchangeable
You see this often with Lists, for example. List is an interface, with at least two very common implementations: ArrayList and LinkedList.

It is perfectly legal to declare

List<T> myList = new ArrayList<>();

just as it is perfectly legal to declare

List<T> myList = new LinkedList<>();

because every LinkedList is, by definition, a List, and every ArrayList is also by definition a List. Since the type on the right fulfils all the conditions of the type on the left, even though they’re different, the assignment is always perfectly legal.

 

 

Saturday, July 5, 2025

GOTO explained

 There is a keyword in Java that can’t be used, and hasn’t been used in modern programming languages for decades, but it’s worth discussing because it brings up a point about how to write good, clean code (hint: by not ever using this keyword anywhere). This is, of course, the goto (or go-to, or GOTO, depending on the language; in Java, the reserved word is “goto” all lowercase, no hyphen) statement condemned by none other than Edgar Dijkstra himself, long before Java was invented.

GOTO was initially devised as a part of a branching or looping instruction (as in: “if this is true, GOTO this line and do xyz, else GOTO this other line and do abc” or “while this is true, do this, then GOTO this place to check the loop condition”).  But, very quickly, problems came about with GOTO as an introducer of spaghetti code.

People used to number the lines of their code, and feed those line numbers into GOTO to tell it where to jump to. Most of the time, people would write successive lines of code separated by 10; the first line was line 10, then 20, then 30, and so on. These gaps  were put in place so that, if there was ever a bug, there would be numeric space in between to, later in the file, insert a fix at an available line number, even out of counting order, and then use go-to to come in or out of the fix as needed.

But, of course, as programs get longer and more complex, mistakes become simultaneously more common and more difficult to fix, so the need for these kinds of goto statements jumping around the code increases rather rapidly, much faster than the length of the code. That is, a piece of code that has twice as many lines, probably has more than twice as many bugs (and therefore needs more than twice as many of these goto statements).


This example is in C, not in Java, but it illustrates the messiness just as well:

#include <stdio.h>

 

int main() {

    int value = 42;

 

    printf("Start of program\n");

 

    // Suppose this block is buggy

    goto skip_buggy_code;

    printf("This buggy code would crash!\n");

    value = value / 0; // Division by zero bug

 

skip_buggy_code:

    printf("Buggy code skipped. Value is %d\n", value);

 

    // Another bug

    if (value == 42) goto avoid_null_pointer;

    int *ptr = NULL;

    printf("Dereferencing null pointer: %d\n", *ptr); // Would crash

 

avoid_null_pointer:

    printf("Null pointer dereference avoided.\n");

 

    printf("End of program\n");

    return 0;

}

Using goto with line numbers (or in this C example—linguistically developed enough to have labels already) creates incredibly messy code that is hard to follow. Imagine trying to read an essay written by a student who writes clearly, versus reading an essay by a student who constantly circles things, crosses them out, indicates that a paragraph is in the wrong place, and so on. The first student’s work would be easier to understand than the second, even if, presentation aside, the second student did bring up some good points in the essay.

This messiness—or, rather, avoiding it—is why Java does not support GOTO behavior, and, further, prevents any use of anything even remotely similar by taking that word out of the list of possibly allowed user-defined symbols. You therefore cannot implement a method with that name, nor give the name to a variable or a class. 

Friday, July 4, 2025

Advent of Code 2022 Day 1 part 2

 Like most AoC second-star problems, this one builds on the first star, and, actually, in this case, requires only minimal changes.

The second star, accessible only if you have completed the first, asks this time for the total consumed by the three hungriest elves—the champion you found for the first star, plus the second- and third-place elves.

That can be done with this very simple method, which, including comments and white space, is not even 10 lines:

private static int getSumOfTopNElements(List<Integer> elfCalories, int maxIndex) {
     int topNElfCals = 0;
     // given a sorted list, take the running sum of the elements up to maxIndex
    
for (int i = 0; i < maxIndex; i++) {
          topNElfCals += elfCalories.get(i);
     }
     // return that sum
    
return topNElfCals;
}

maxIndex in this case is always 3, and a good IDE like IntelliJ (even the Community Edition) will flag this. But I have a reason for building it like this, and not hardcoding the three.

Building the method like this, and allowing any index to be passed—even if we know that, for the elves’ purposes, that index will always be 3—allows the code to be much more portable, and called on by someone else in totally different circumstances who needs to find the total of the first few elements of a sorted list of Grades or Salaries or SquareFootages or what have you. This way, you do not care what the list is for—it could be elves and their diets, or anything else—nor do you care about which index you go to.

There’s another reason for writing like this over hardcoding a 3 or another number: if you hard-code it, you’ll have to remember to find and change every hard-coded instance, in order not to have inconsistencies, if you ever want more or less than the top 3. But if you pass in the 3 (or any other number) dynamically as we have done here, you make it so that the number can change freely without ever having to make those changes—or, by greatly diminishing the number of places where code would have to change if you wanted to modify the logic. It is a good idea to write as generically as possible, hardcoding as little as possible, because each time you hard-code something, you make your code a little more restrictive, harder to maintain, and harder to understand.

 Here's the whole implementation:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ElfCalorieCounter {

     public static void main(String[] args) throws IOException {

          // create a reader object to read through the input file
         
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\andre\\IdeaProjects\\JavaPractice\\src\\main\\java\\elfCalories.txt"));

          // calculate all the calorie counts of the elves
         
List<Integer> elfCalories = calculateCalories(reader);
          reader.close();


          // sort, then reverse the list
          prepareList
(elfCalories);

          // find the sum of the top 3 best-performing elves
         
int top3Cals = getSumOfTopNElements(elfCalories, 3);

          // display the calorie counts of all elves
         
System.out.println("Calories per elf: " + elfCalories);

          // display the calorie count of the 3 most productive, summed together
         
System.out.println("The calories collected by the top 3 most productive elves: " + top3Cals);
          System.out.println("elfCalories.size() = " + elfCalories.size());
     }

     private static int getSumOfTopNElements(List<Integer> elfCalories, int maxIndex) {
          int topNElfCals = 0;
          // given a sorted list, take the running sum of the elements up to maxIndex
         
for (int i = 0; i < maxIndex; i++) {
               topNElfCals += elfCalories.get(i);
          }
          // return that sum
         
return topNElfCals;
     }

     private static void prepareList(List<Integer> elfCalories) {
          Collections.sort(elfCalories);
          Collections.reverse(elfCalories);
     }

     /**
      * @param reader BufferedReader object reads file line by line
      * @return a list containing the calorie counts of various elves
      */
    
private static List<Integer> calculateCalories(BufferedReader reader) throws IOException {
          String line;
          List<Integer> elfCalories = new ArrayList<>();
          int totalCalories = 0;

          // while there are still lines of calories
         
while ((line = reader.readLine()) != null) {
               int calories;
               // when you hit a blank line, a given elf is done collecting calories
               // add its total to the list and zero out the total to get ready for the next elf
              
if (line.isEmpty()) {
                    elfCalories.add(totalCalories);
                    totalCalories = 0;
               }
               // but if the same elf is still collecting calories, then the elf's running total of calories
               // is how many calories they've collected up until now, plus the calories they just collected on this line
              
else {
                    calories = Integer.parseInt(line);
                    totalCalories += calories;
               }
          }
          // return a list of calorie counts where 1 number = the production of 1 elf
         
return elfCalories;
     }
}

Advent of Code 2022 Day 1 part 1

 This time, we’re going to look at the first submission—for which I only got one star at the time, not both—from the first day of the 2022 contest. (Again, you can do these puzzles whenever you want once they’ve been made public.)


In 2022, the problem was quite straightforward:

  • There are a lot of elves, and those elves eat meals and ingest calories
  • We know how many calories are in each food eaten by each elf
  • We have a way to delimit one elf from another in the input
  • We want to know how many calories the elf who ate the most consumed across all meals eaten by that elf

As with the previous Advent of Code articles, the best place to start is always with the sample they give you, which on that day was:

1000
2000
3000

4000

5000
6000

7000
8000
9000

10000


The rules of the problem dictate that this means there are 5 elves:

a.       One ate 3 meals of 1000, 2000, and 3000 calories

b.       One ate 1 meal of 4000 calories

c.       One ate 2 meals of 5000 and 6000 calories

d.       One ate 3 meals of 7000, 8000, and 9000 calories

e.       One ate 1 meal of 10000 calories

At this small scale, the problem is easy to solve, even relying on mental math alone: Which elf ate the most? Naturally, the elf who ate 7000+8000+9000=24000 calories (since the others ate 6000, 4000, 11000, 10000)

The first order of business, like in every AoC puzzle, is to read the input file:

This particular year, unlike the more recent args[0] solution, I actually passed in the path directly, without going through args, like this:

BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\andre\\IdeaProjects\\AdventOfCode\\elf-input.txt"));

Passing the path like this, or passing the path in through args, as long as both are done correctly, are equivalent methods

The key method is the following:

    private static List<Integer> calculateCalories(BufferedReader reader) throws IOException {

          String line;

          List<Integer> elfCalories = new ArrayList<>();

          int totalCalories = 0;

 

          // while there are still lines of calories

          while ((line = reader.readLine()) != null) {

               int calories;

               //When you hit a blank line, a given elf is done collecting calories

               // add its total to the list and zero out the total to get ready for the next elf

               if (line.equals("")) {

                    elfCalories.add(totalCalories);

                    totalCalories = 0;

               }

               // but if the same elf is still collecting calories, then the elf's running total of calories

               // is how many calories they've collected up until now, plus the calories they just collected on this line

               else {

                    calories = Integer.parseInt(line);

                    totalCalories += calories;

               }

          }

          // return a list of calorie counts where 1 number = the production of 1 elf

          return elfCalories;

     }

The key to solving the problem is making explicit what we know is obvious as human beings—that a blank line separates elf A from elf B from elf C, and so on. That is, when you see a blank line, stop attributing those calories to the current elf, and instead create a new elf to start assigning them to.

Then, since we need the top elf, we need the list to be sorted so that the top elf’s calorie count is somewhere predictable.

So it's perfectly reasonable for      private static void prepareList(List<Integer> elfCalories) {} to include the following line:

          Collections.sort(elfCalories);

But there’s actually a hidden problem with that—the best-performing elf is now last, not first, on account of the default sorting order built into Collections.sort(). However, this problem is easily fixed by using another method in Collections—namely, Collections.reverse(). Calling sort() and then calling reverse() gets us the list sorted in an order such that the hungriest elf is first.
 
The sample has 5 distinct elves; the real problem file has 250. As with other AoC problems, it’s possible to solve this elf problem in any language, or none at all—but I would strongly caution against a purely manual solution simply on account of how long it would take.

All together, the solution looks like:


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ElfCalorieCounter {

     public static void main(String[] args) throws IOException {

          // create a reader object to read through the input file
         
BufferedReader reader = new BufferedReader(new FileReader("C:\\Users\\andre\\IdeaProjects\\JavaPractice\\src\\main\\java\\elfCalories.txt"));

          // calculate all the calorie counts of the elves
         
List<Integer> elfCalories = calculateCalories(reader);
          reader.close();


          // sort, then reverse the list
          prepareList
(elfCalories);


          //Display the calorie counts of all elves
         
System.out.println("Calories per elf: " + elfCalories);
     }

     private static void prepareList(List<Integer> elfCalories) {
          Collections.sort(elfCalories);
          Collections.reverse(elfCalories);
     }

     /**
      * @param reader BufferedReader object reads file line by line
      * @return a list containing the calorie counts of various elves
      */
    
private static List<Integer> calculateCalories(BufferedReader reader) throws IOException {
          String line;
          List<Integer> elfCalories = new ArrayList<>();
          int totalCalories = 0;

          // while there are still lines of calories
         
while ((line = reader.readLine()) != null) {
               int calories;
               //When you hit a blank line, a given elf is done collecting calories
               // add its total to the list and zero out the total to get ready for the next elf
              
if (line.isEmpty()) {
                    elfCalories.add(totalCalories);
                    totalCalories = 0;
               }
               // but if the same elf is still collecting calories, then the elf's running total of calories
               // is how many calories they've collected up until now, plus the calories they just collected on this line
              
else {
                    calories = Integer.parseInt(line);
                    totalCalories += calories;
               }
          }
          // return a list of calorie counts where 1 number = the production of 1 elf
         
return elfCalories;
     }
}

 

 

Thursday, July 3, 2025

Advent of Code 2024 Day 1 part 2

 To get their big prize at Christmas, the elves you’re helping by solving these puzzles need you to solve two puzzles a day—the second always uses the same input as the first—to get 50 stars by Christmas. (Of course, you can solve the puzzles anytime after they’re released, and there’s no penalty for solving a puzzle outside of the December in which it came out.)


The second part of the December 1, 2024 puzzle, which you’ll only see if you finish the first one, deals with what the elves call “similarity.” That doesn’t mean anything outright, but they give a definition for this puzzle specifically: the product of a left-list number and the number of times it shows up in the right list.

All the other logic can stay—you still need to take advantage of the reading of the file into two lists, anyway.

What changes in part 2 is that you now have an extra method for calculating this new metric:

     public int calculateSimilarity(List<Integer> leftList, List<Integer> rightList) {

          int similarity = 0;

 

          // multiply each number in the left list by how many occurrences it has in the right list

          // and keep a running total of these products

          for (int elem : leftList) {

               int count = frequency(rightList, elem);

               similarity += (count * elem);

          }

 

          // return the total

          return similarity;

     }

The easiest way to count the number of occurrences is the way I’ve done it here—with Collections.frequency(). Collections.frequency() takes in two parameters: the collection through which you want to search, and the element for which you want to search within that collection; it returns the number of times the element appears.

The loop, therefore:

·       Goes through every element of the left list

·       Determines the frequency of that element in the left list

·       Multiplies the frequency by the element’s own value

·       Adds that product to the global measurement of similarity

·       Returns the total once everything has been processed


The whole code for Day 1, parts 1 and 2, in 2024, is as follows:

package org.example.c01;

 

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

 

import static java.util.Collections.*;

 

public class Solution {

     static List<Integer> leftList = new ArrayList<>();

     static List<Integer> rightList = new ArrayList<>();

 

     public static void main(String[] args) throws IOException {

          Solution solution = new Solution();

          solution.readFile(args[0]);

          sort(leftList);

          sort(rightList);

 

          int result = solution.calculateDistance(leftList, rightList);

          System.out.println("total distance: " + result);

 

          int similarity = solution.calculateSimilarity(leftList, rightList);

          System.out.println("similarity = " + similarity);

     }

 

     private void readFile(String fileName) throws IOException {

          BufferedReader reader = new BufferedReader(new FileReader(fileName));

 

          String line;

          while ((line = reader.readLine()) != null) {

               String[] arr = line.split("\t");

               leftList.add(Integer.parseInt(arr[0]));

               rightList.add(Integer.parseInt(arr[1]));

          }

     }

 

     public int calculateDistance(List<Integer> leftList, List<Integer> rightList) {

          int totalDistance = 0;

 

          // pair the smallest numbers together

          // then the next smallest

          // etc.

          // and calculate the running sum of the absolute distances between the pairs

          for (int i = 0; i < leftList.size(); i++) {

               totalDistance += Math.abs(leftList.get(i) - rightList.get(i));

          }

 

 

          // return the sum

          return totalDistance;

     }

 

     public int calculateSimilarity(List<Integer> leftList, List<Integer> rightList) {

          int similarity = 0;

 

 

          // multiply each number in the left list by how many ocurrences it has in the right list

          // and keep a running total of these products

          for (int elem : leftList) {

               int count = frequency(rightList, elem);

               similarity += (count * elem);

          }

 

          // return the total

          return similarity;

     }

 

 

}

Advent of Code 2024 Day 1 part 1

 Each year, a team spends months devising and testing problems. Those problems are then released one at a time from December 1 to 25 as the Advent of Code contest. Over the next few articles, let’s look at solutions to some easy old problems from previous years.

The problems generally get harder as the month progresses, so these next few articles will cover December 1’s problems from different years.

These problems, in theory, can be solved in any language, or without any language at all, completely by hand (though I don’t recommend that). What matters most to get your star rewards is that you get the right answer by some legitimate means and that you have fun along the way.

In 2024—there’s a more elaborate story—you essentially have 2 lists of numbers which are in the wrong order. Getting them into the proper order is essential, as is pairing them up, subtracting them, and returning the grand total sum of all those differences.

I’ll give you my approach:

Lists of numbers look something like this:

3   4

4   3

2   5

1   3

3   9

3   3

(This was the actual sample list that the contest gave me). I cannot stress enough how important it is to start solving these problems both from the sample list and by hand, before attempting to write any actual code, in Java or whatever language you wish.

The problem is the following:
  • We need to sort the left
  • We need to sort the right
  • We need to subtract the first sorted left from the first sorted right, then add that to the total
  • We need to subtract the second sorted left from the second sorted right, then add that to the total
  • And so on

My first thought was that we need to keep track of two global lists—one for the left, and one for the right.

     static List<Integer> leftList = new ArrayList<>();

     static List<Integer> rightList = new ArrayList<>();

 

We need to read a file—be it that test file, or the actual answer file, which is different for everyone, but which has 1000 numbers in each list. Reading a file will allow us to populate leftList and rightList.  

I cannot stress enough how important it is that you start by reading in a much smaller file than the actual input file; this is why the organizers gave a much smaller sample while explaining the problem.

readFile() is relatively straightforward, using BufferedReader and a loop:

private void readFile(String fileName) throws IOException {

          BufferedReader reader = new BufferedReader(new FileReader(fileName));

 

          String line;

          while ((line = reader.readLine()) != null) {

               String[] arr = line.split("\t");

               leftList.add(Integer.parseInt(arr[0]));

               rightList.add(Integer.parseInt(arr[1]));

          }

     }

line.split() takes what’s called a regular expression (often shortened just to RegEx—more on them in a future post) and splits the line whenever it finds that regex delimeter. Here, the regex is “\t”, which means any tab character.

Integer.parseInt() takes a String and converts it to an integer: the String “137” becomes the integer 137, and so on.

Since the file looks like

10\t20
24\t2798
5789\t3387
etc.

It will be split, line by line, into an array with two elements, which, thanks to parseInt(), will be integers.
10 and 20
24 and 2798
5789 and 3387
etc.

leftList gets the first element of that array, and rightList gets the second element.

Lists are Collections, so they have access to the method Collections.sort() for sorting their elements.

You can see it used here, in the main method:

  public static void main(String[] args) throws IOException {

          Solution solution = new Solution();

          solution.readFile(args[0]);

          sort(leftList); // this is a call to Collections.sort()

          sort(rightList); // this is a call to Collections.sort()

 

          int result = solution.calculateDistance(leftList, rightList);

          System.out.println("total distance: " + result);

     }

 

The method that actually calculates the “distances” is quite simple—most of this method is either comments or whitespace:

·       Start the distance counter at 0

·       In a loop, get the appropriate elements—with the same index—from each list (the firsts, the seconds, the thirds, etc.)

·       Subtract them

·       Take the absolute value, because what you care about is the distance between the numbers, not which list’s number is bigger or smaller

·       Keep adding those distances to the counter

·       When you’ve processed the whole list, return the value of the counter

Here it is in Java:


public int calculateDistance(List<Integer> leftList, List<Integer> rightList) {

          int totalDistance = 0;

 

          // pair the smallest numbers together

          // then the next smallest

          // etc.

          // and calculate the running sum of the absolute distances between the pairs

          for (int i = 0; i < leftList.size(); i++) {

               totalDistance += Math.abs(leftList.get(i) - rightList.get(i));

          }

          // return the sum

          return totalDistance;

     }

Now, we have all the logic we need to earn one star (out of two) for this first problem in 2024.

There’s just one more thing we have to do: handle args[0].

Recall that the main method—which must be present as public static void main(String[] args) in order for the program to be executable (the name of the array can change, but nothing else in the signature).

In IntelliJ, next to the play button and the ladybug, there are 3 vertical dots. Click on them. You should now see the following pop-up:

A screenshot of a computer

AI-generated content may be incorrect.

We care about, in this case, the “Program arguments” field. args is an array, naturally, of arguments, which we pass to main(). It’s perfectly fine not to pass anything most of the time, but now that we explicitly want to, this is how you would get that done in IntelliJ.

The program has been written in such a way that args[0] is expected to be the path to the file where the puzzle input is stored. Into that field, type in the path. Click OK, and the dialog box will close. Run the program.

 

Here's all the code for part 1:

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.util.ArrayList;

import java.util.List;

 

import static java.util.Collections.*;

 

public class Solution {

     static List<Integer> leftList = new ArrayList<>();

     static List<Integer> rightList = new ArrayList<>();

 

     public static void main(String[] args) throws IOException {

          Solution solution = new Solution();

          solution.readFile(args[0]);

          sort(leftList);

          sort(rightList);

 

          int result = solution.calculateDistance(leftList, rightList);

          System.out.println("total distance: " + result);

     }

 

     private void readFile(String fileName) throws IOException {

          BufferedReader reader = new BufferedReader(new FileReader(fileName));

 

          String line;

          while ((line = reader.readLine()) != null) {

               String[] arr = line.split("\t");

               leftList.add(Integer.parseInt(arr[0]));

               rightList.add(Integer.parseInt(arr[1]));

          }

     }

 

     public int calculateDistance(List<Integer> leftList, List<Integer> rightList) {

          int totalDistance = 0;

 

          // pair the smallest numbers together

          // then the next smallest

          // etc.

          // and calculate the running sum of the absolute distances between the pairs

          for (int i = 0; i < leftList.size(); i++) {

               totalDistance += Math.abs(leftList.get(i) - rightList.get(i));

          }

          // return the sum

          return totalDistance;

     }

Switch

 Other than if/if-else/if-else if-else and the ternary operator, there is yet another common and important conditional expression in Java th...