# Kirk Haines # Ruby/Crystal/Rust Advent of Code 2022 - Day 1

Welcome to my Advent of Code adventure for 2022! This year, I am going to solve the Advent of Code challenges using three different languages - Ruby, Crystal, and Rust. The goal is not to golf for the most terse answer in each language, but rather to implement reasonably idiomatic, but conceptually similar solutions in each language, and to use that as a tool to compare and contrast the languages. So, without further adieu, let's get started!

## The Repository

All of the code for this day's solutions, as well as the data input file, can be found at:

## Part 1

To see the full details of the challenge, visit the Advent of Code 2022 - Day 1 page. However, the tl;dr version of the challenge is as follows:

In this puzzle, a group of Elves is going on an expedition to collect star fruit for Santa's reindeer. Each Elf is carrying food items with different numbers of calories. The puzzle is to determine which Elf is carrying the most calories and how many total calories they are carrying. The calories of each food item are listed in a sequence of numbers, with a blank line separating the inventory of each Elf. To solve the puzzle, the numbers must be read and the total calories for each Elf must be calculated. The Elf with the highest total calories is the answer to the puzzle.

So, for example, given the following input:

``````1000
2000
3000

4000

5000
6000

7000
8000
9000

10000
``````

The fourth elf would be carrying `7000 + 8000 + 9000 == 24000` calories and that would be the answer to this puzzle.

### The Approach

The approach to solving this puzzle involves the following steps:

2. split it into chunks delimited by repeated newlines

3. map each chunk into an array of integers

4. sum the individual arrays of integers

5. find the largest sum

This is a pretty straightforward approach, and it can be implemented in Ruby, Crystal, and Rust in similar ways.

#### Ruby solution

The Ruby solution that I wrote looks like this:

``````the_most_calories = File.read('input.txt')
.split(/\n\n/)
.map do |group|
group
.strip
.split(/\n/)
.map(&:to_i)
end
.map(&:sum)
.max

puts the_most_calories
``````

The solution breaks down into the following steps.

``````the_most_calories = File.read('input.txt')
.split(/\n\n/)
``````

This solution assumes that the input is in a file called `input.txt`.

That file is read into memory and then split into chunks delimited by repeated newlines (i.e. a blank line).

``````.map do |group|
group
.strip
.split(/\n/)
.map(&:to_i)
end
``````

The resulting array is then mapped, splitting each chunk into an array of text lines, which are then converted, via `map(&:to_i)`, into an array of integers. At this point, the program holds an array of arrays of integers.

``````                        .map(&:sum)
.max
``````

The next step is to sum each of the inner arrays, and then to find the maximum of those sums. This is done via the `map(&:sum).max` method calls.

#### Crystal solution

For this first task, the Crystal solution is nearly identical to the Ruby solution:

``````the_most_calories = File.read("input.txt")
.split(/\n\n/)
.map do |group|
group
.strip
.split(/\n/)
.map(&.to_i)
end
.map(&.sum)
.max

puts the_most_calories
``````

The only difference is that the `&.` is used instead of the `&:` syntax for the `map()` inline method calls and double quotes are used around the filename instead of single quotes.

#### Rust solution

The Rust solution is conceptually identical to the Ruby and Crystal solutions, but the syntax is a bit different, and there is some additional explicit type annotation that is required.

``````use std::fs;

fn main() {
.unwrap()
.split("\n\n")
.map(|group| {
group
.trim()
.split("\n")
.map(|line| line.parse::<i32>().unwrap())
})
.map(|numbers| numbers.sum())
.max()
.unwrap();

println!("{}", the_most_calories);
}
``````

The most interesting aspect, for me, of solving this with Rust was just how similar the Rust solution ended up being to the Ruby and the Crystal solutions. Rust is more verbose, it requires more ceremony, and its compiler's type inference requires more explicit type annotations than Crystal requires, but the overall structure of the solution still ends up being very similar to the other two, down to the names of the methods that are used in the solution.

Because a Rust program requires a `main()` function to execute anything, the program structure can't be quite so simple as the Ruby and Crystal structures, but for this first task, I just put all of the logic directly into `main()`.

``````let the_most_calories: i32 = fs::read_to_string("input.txt")
.unwrap()
.split("\n\n")
``````

As with the previous implementations, the first thing to be done is to read the input file, and to split it into chunks of lines, delimited by a double newline. The unwrap() call is used because fs::read_to_string() returns a `Result`, which `unwrap()` handles, returning either the data contained in the `Result`, or a `panic` (an error) of the result is `None`.

``````.map(|group| {
group
.trim()
.split("\n")
.map(|line| line.parse::<i32>().unwrap())
})
``````

The next step, as with the previous examples, is to split each of the chunks of lines into individual lines, and convert them to integers. The syntax is a little more involved, however. The first significant difference is that `trim()` call. With Ruby and with Crystal, whitespace is ignored when text is being cast to an integer via `to_i()`. With Rust, however, if unexpected whitespace is encountered, you can get an error:

``````thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseIntError { kind: Empty }', ./day_1_2.rs:11:49
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
``````

Using the `trim()` method ensures that the input is clean. Following that, it is split just like with the other languages, and then, like the other languages, a `map()` call is made. The specific syntax to transform text to an integer is different, but the end result is the same. Once that `map()` runs, the array of text chunks has become an array of arrays of integers.

``````        .map(|numbers| numbers.sum())
.max()
.unwrap();
``````

That final `map()` sums the array of integers, replacing it with the result of that call, and the `max()` method call that follows finds the maximum value in the array.

That's pretty straightforward. There are some small syntax differences, but ultimately the Rust implementation doesn't stray too far from the Ruby and the Crystal versions.

## Part 2

The gist of the second part of the puzzle is as follows:

The Elves are interested in finding the sum of the Calories carried by the top three Elves carrying the most Calories to avoid running out of snacks. In the example data given, the top three Elves are the fourth Elf (with 24000 Calories), then the third Elf (with 11000 Calories), then the fifth Elf (with 10000 Calories). The sum of the Calories carried by these three elves is 45000.

### The Approach

The approach to solving this puzzle is nearly identical to the approach used to solve part 1. The steps are as follows:

2. split it into chunks delimited by repeated newlines

3. map each chunk into an array of integers

4. sum the individual arrays of integers

5. sort the list of sums

6. take the last three elements of the list

Here is how to make those changes to the Ruby, Crystal, and Rust solutions.

#### Ruby solution

``````the_most_calories = File.read('input.txt')
.split(/\n\n/)
.map do |group|
group
.strip
.split(/\n/)
.map(&:to_i)
end
.map(&:sum)
.sort
.last(3)

puts the_most_calories.inspect
puts the_most_calories.sum
``````

The only difference is at the end of the chain of methods. The `sort` method is used to sort the list of sums, and then the `last(3)` method is used to select the last three elements of the list.

#### Crystal solution

``````the_most_calories = File.read("input.txt")
.split(/\n\n/)
.map do |group|
group
.strip
.split(/\n/)
.map(&.to_i)
end
.map(&.sum)
.sort
.last(3)

puts the_most_calories.inspect
puts the_most_calories.sum
``````

Again, the only difference is that the `sort` method is used to sort the list of sums, and then the `last(3)` method is used to select the last three elements of the list.

#### Rust solution

``````use std::fs;

fn main() {
let mut the_most_calories: Vec<i32> = fs::read_to_string("input.txt")
.unwrap()
.split("\n\n")
.map(|group| {
group
.trim()
.split("\n")
.map(|line| line.parse::<i32>().unwrap())
.sum()
})
.collect::<Vec<_>>();
the_most_calories.sort_by(|a, b| b.cmp(a));
the_most_calories.truncate(3);

println!(
"{:?}\n{}",
the_most_calories,
the_most_calories.iter().sum::<i32>()
);
}
``````

The implementation for Rust again follows closely the implementations for Ruby and for Crystal, with one notable difference. In the Crystal and the Ruby versions, it was possible to chain all of the method calls back to back, because Ruby and Crystal both have `sort` methods that return the sorted array. With Rust, however, the various sorting methods such as `sort()` and `sort_by()` act on their argument directly, sorting the elements in place, and returning `()` instead of the array. The `truncate()` method does the same. Thus, in the Rust example, we break the call chain and use separate statements to sort and to truncate.

``````    the_most_calories.sort_by(|a, b| b.cmp(a));
the_most_calories.truncate(3);
``````

## Conclusion

This was a fun puzzle, and it served as a good introduction to the general ways in which conceptually similar code can vary between the chosen languages, particularly between Rust and the other two languages. The key differences with the Rust version were the need to explicitly annotate the types of variables, and the need to explicitly unwrap the `Result` values returned by several of the methods, but the solutions were otherwise quite similar across all three languages.