mirror of
https://github.com/mfocko/blog.git
synced 2024-11-13 01:30:29 +01:00
blog(aoc-2022): add 2nd week
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
783a8091b1
commit
5db41d357f
1 changed files with 661 additions and 0 deletions
661
blog/aoc-2022/02-week-2.md
Normal file
661
blog/aoc-2022/02-week-2.md
Normal file
|
@ -0,0 +1,661 @@
|
|||
---
|
||||
title: 2nd week of Advent of Code '22 in Rust
|
||||
description: Surviving second week in Rust.
|
||||
date: 2022-12-25T23:15
|
||||
slug: aoc-2022/2nd-week
|
||||
authors:
|
||||
- name: Matej Focko
|
||||
title: "a.k.a. @mf"
|
||||
url: https://gitlab.com/mfocko
|
||||
image_url: https://github.com/mfocko.png
|
||||
tags:
|
||||
- aoc-2022
|
||||
- advent-of-code
|
||||
- rust
|
||||
hide_table_of_contents: false
|
||||
---
|
||||
|
||||
Let's go through the second week of [_Advent of Code_] in Rust.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## [Day 8: Treetop Tree House](https://adventofcode.com/2022/day/8)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
We get a forest and we want to know how many trees are visible from the outside.
|
||||
Apart from that we want to find the best view.
|
||||
|
||||
:::
|
||||
|
||||
Nothing interesting. We are moving around 2D map though. And indexing can get a
|
||||
bit painful when doing so, let's refactor it a bit ;) During the preparation for
|
||||
the AoC, I have written `Vector2D` and now it's time to extend it with indexing
|
||||
of `Vec` of `Vec`s. In my solution I was manipulating with indices in the following
|
||||
way:
|
||||
|
||||
- swapping them
|
||||
- checking whether they are correct indices for the `Vec<Vec<T>>`
|
||||
- indexing `Vec<Vec<T>>` with them
|
||||
|
||||
:::caution
|
||||
|
||||
I'm getting familiar with Rust and starting to „abuse“ it… While doing so, I'm
|
||||
also uncovering some „features“ that I don't really like. Therefore I will mark
|
||||
all of my rants with _thicc_ **«↯»** mark and will try to „lock“ them into their
|
||||
own „box of hell“.
|
||||
|
||||
:::
|
||||
|
||||
#### Swapping indices
|
||||
|
||||
Relatively simple implementation, just take the values, swap them and return new
|
||||
vector.
|
||||
|
||||
```rust
|
||||
impl<T: Copy> Vector2D<T> {
|
||||
pub fn swap(&self) -> Self {
|
||||
Self {
|
||||
x: self.y,
|
||||
y: self.x,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pretty straight-forward implementation, but let's talk about the `T: Copy`. We
|
||||
need to use it, since we are returning a **new** vector, with swapped **values**.
|
||||
If we had values that cannot be copied, the only thing we could do, would be a
|
||||
vector of references (and it would also introduce a lifetime, to which we'll get
|
||||
later on). This is pretty similar with the operations on sets from the first week.
|
||||
|
||||
#### Indexing `Vec`
|
||||
|
||||
I will start with the indexing, cause bound-checking is a bit more… complicated
|
||||
than I would like to.
|
||||
|
||||
```rust
|
||||
pub fn index<'a, T, U>(v: &'a [Vec<U>], idx: &Vector2D<T>) -> &'a U
|
||||
where
|
||||
usize: TryFrom<T>,
|
||||
<usize as TryFrom<T>>::Error: Debug,
|
||||
T: Copy,
|
||||
{
|
||||
let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());
|
||||
&v[y][x]
|
||||
}
|
||||
```
|
||||
|
||||
Let's talk about this mess… Body of the function is probably the most easy part
|
||||
and should not be hard to understand, we just take the `x` and `y` and convert
|
||||
them both to `usize` type that can be used later on for indexing.
|
||||
|
||||
The type signature of the function is where the fun is at :wink: We are trying
|
||||
to convert unknown type to `usize`, so we must bound the `T` as a type that can
|
||||
be converted to `usize`, that's how we got `usize: TryFrom<T>` which basically
|
||||
says that `usize` must implement `TryFrom<T>` trait vand therefore allows us to
|
||||
convert the indices to actual `usize` indices. Using `.unwrap()` also forces us
|
||||
to bound the error that can occur when converting `T` into `usize`, that's how
|
||||
we get `<usize as TryFrom<T>>::Error: Debug` which loosely means
|
||||
|
||||
> error during conversion of `T` into `usize` must implement `Debug`,
|
||||
> i.e. can be printed in some way or other
|
||||
|
||||
`T: Copy` is required by `.try_into()` which takes `T` by-value.
|
||||
|
||||
And now we are left only with the first line of the definition.
|
||||
|
||||
:::note
|
||||
|
||||
Skilled Rustaceans might notice that this implemention is rather flaky and can
|
||||
break in multiple places at once. I'll get back to it…
|
||||
|
||||
:::
|
||||
|
||||
Let's split it in multiple parts:
|
||||
- `v: &'a [Vec<U>]` represents the 2D `Vec`, we are indexing, `Vec` implements
|
||||
`Slice` trait and _clippy_ recommends using `&[T]` to `&Vec<T>`, exact details
|
||||
are unknown to me
|
||||
- `idx: &Vector2D<T>` represents the _indices_ which we use, we take them by
|
||||
reference to avoid an unnecessary copy
|
||||
- `-> &'a U` means that we are returning a _reference_ to some value of type `U`.
|
||||
Now the question is what does the `'a` mean, we can also see it as a generic
|
||||
type declared along `T` and `U`. And the answer is _relatively_ simple, `'a`
|
||||
represents a _lifetime_. We take the `v` by a reference and return a reference,
|
||||
borrow checker validates all of the _borrows_ (or references), so we need to
|
||||
specify that our returned value has _the same lifetime_ as the vector we have
|
||||
taken by a reference, i.e. returned reference must live at least as long as the
|
||||
`v`. This way we can „be sure“ that the returned reference is valid.
|
||||
|
||||
##### Issues
|
||||
|
||||
First issue that our implementation has is the fact that we cannot get a mutable
|
||||
reference out of that function. This could be easily resolved by introducing new
|
||||
function, e.g. `index_mut`. Which I have actually done while writing this part:
|
||||
```rust
|
||||
pub fn index_mut<'a, T, U>(v: &'a mut [Vec<U>], idx: &Vector2D<T>) -> &'a mut U
|
||||
where
|
||||
usize: TryFrom<T>,
|
||||
<usize as TryFrom<T>>::Error: Debug,
|
||||
T: Copy,
|
||||
{
|
||||
let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());
|
||||
&mut v[y][x]
|
||||
}
|
||||
```
|
||||
|
||||
:::caution **«↯»** Why can't we use one function?
|
||||
|
||||
When we consider a `Vec<T>`, we don't need to consider containers as `T`, Rust
|
||||
implements indexing as traits `Index<T>` and `IndexMut<T>` that do the dirty work
|
||||
behind syntactic sugar of `container[idx]`.
|
||||
|
||||
However, implementing of traits is not allowed for _external_ types, i.e. types
|
||||
that you haven't defined yourself. This means that you can implement indexing
|
||||
over containers that you have implemented yourself, but you cannot use your own
|
||||
types for indexing „built-in“ types.
|
||||
|
||||
Another part of this rabbit hole is trait `SliceIndex<T>` that is of a relevance
|
||||
because of
|
||||
```rust
|
||||
impl<T, I> Index<I> for [T]
|
||||
where
|
||||
I: SliceIndex<[T]>
|
||||
|
||||
impl<T, I, A> Index<I> for Vec<T, A>
|
||||
where
|
||||
I: SliceIndex<[T]>,
|
||||
A: Allocator
|
||||
|
||||
impl<T, I, const N: usize> Index<I> for [T; N]
|
||||
where
|
||||
[T]: Index<I>
|
||||
```
|
||||
|
||||
In other words, if your type implements `SliceIndex<T>` trait, it can be used
|
||||
for indexing. As of now, this trait has all of its required methods experimental
|
||||
and is marked as `unsafe`.
|
||||
|
||||
:::
|
||||
|
||||
Another problem is a requirement for indexing either `[Vec<T>]` or `Vec<Vec<T>>`.
|
||||
This requirement could be countered by removing inner type `Vec<T>` and constraining
|
||||
it by a trait `Index` (or `IndexMut` respectively) in a following way
|
||||
```rust
|
||||
pub fn index<'a, C, T>(v: &'a [C], idx: &Vector2D<T>) -> &'a C::Output
|
||||
where
|
||||
usize: TryFrom<T>,
|
||||
<usize as TryFrom<T>>::Error: Debug,
|
||||
T: Copy,
|
||||
C: Index<usize>
|
||||
{
|
||||
let (x, y): (usize, usize) = (idx.x.try_into().unwrap(), idx.y.try_into().unwrap());
|
||||
&v[y][x]
|
||||
}
|
||||
```
|
||||
|
||||
Given this, we can also give a more meaningful typename for indexing type, such
|
||||
as `I`.
|
||||
|
||||
#### Checking bounds
|
||||
|
||||
Now we can get to the boundary checks, it is very similar, but a more… dirty.
|
||||
First approach that came up was to convert the indices in `Vector2D` to `usize`,
|
||||
but when you add the indices up, e.g. when checking the neighbors, you can end
|
||||
up with negative values which, unlike in C++, causes an error (instead of underflow
|
||||
that you can use to your advantage; you can easily guess how).
|
||||
|
||||
So how can we approach this then? Well… we will convert the bounds instead of
|
||||
the indices and that lead us to:
|
||||
```rust
|
||||
pub fn in_range<T, U>(v: &[Vec<U>], idx: &Vector2D<T>) -> bool
|
||||
where
|
||||
usize: TryInto<T>,
|
||||
<usize as TryInto<T>>::Error: Debug,
|
||||
T: PartialOrd + Copy,
|
||||
{
|
||||
idx.y >= 0.try_into().unwrap()
|
||||
&& idx.y < v.len().try_into().unwrap()
|
||||
&& idx.x >= 0.try_into().unwrap()
|
||||
&& idx.x
|
||||
< v[TryInto::<usize>::try_into(idx.y).unwrap()]
|
||||
.len()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
You can tell that it's definitely a shitty code. Let's improve it now! We will
|
||||
get back to the original idea, but do it better. We know that we cannot convert
|
||||
negative values into `usize`, **but** we also know that conversion like that
|
||||
returns a `Result<T, E>` which we can use to our advantage.
|
||||
```rust
|
||||
pub fn in_range<T, U>(v: &[Vec<U>], idx: &Vector2D<T>) -> bool
|
||||
where
|
||||
T: Copy,
|
||||
usize: TryFrom<T>,
|
||||
{
|
||||
usize::try_from(idx.y)
|
||||
.and_then(|y| usize::try_from(idx.x).map(|x| y < v.len() && x < v[y].len()))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
```
|
||||
|
||||
`Result<T, E>` is a type similar to `Either` in Haskell and it allows us to chain
|
||||
multiple operations on correct results or propagate the original error without
|
||||
doing anything. Let's dissect it one-by-one.
|
||||
|
||||
`try_from` is a method implemented in `TryFrom` trait, that allows you to convert
|
||||
types and either successfully convert them or fail (with a reasonable error). This
|
||||
method returns `Result<T, E>`.
|
||||
|
||||
We call `and_then` on that _result_, let's have a look at the type signature of
|
||||
`and_then`, IMO it explains more than enough:
|
||||
```rust
|
||||
pub fn and_then<U, F>(self, op: F) -> Result<U, E>
|
||||
where
|
||||
F: FnOnce(T) -> Result<U, E>
|
||||
```
|
||||
|
||||
OK… So it takes the result and a function and returns another result with
|
||||
different value and different error. However we can see that the function, which
|
||||
represents an operation on a result, takes just the value, i.e. it doesn't care
|
||||
about any previous error. To make it short:
|
||||
|
||||
> `and_then` allows us to run an operation, which can fail, on the correct result
|
||||
|
||||
We parsed a `y` index and now we try to convert the `x` index with `try_from`
|
||||
again, but on that result we use `map` rather than `and_then`, why would that be?
|
||||
|
||||
```rust
|
||||
pub fn map<U, F>(self, op: F) -> Result<U, E>
|
||||
where
|
||||
F: FnOnce(T) -> U
|
||||
```
|
||||
|
||||
Huh… `map` performs an operation that **cannot** fail. And finally we use
|
||||
`unwrap_or` which takes the value from result, or in case of an error returns the
|
||||
default that we define.
|
||||
|
||||
How does this work then? If `y` is negative, the conversion fails and the error
|
||||
propagates all the way to `unwrap_or`, if `y` can be a correct `usize` value, then
|
||||
we do the same with `x`. If `x` is negative, we propagate the error as with `y`,
|
||||
and if it's not, then we check whether it exceeds the higher bounds or not.
|
||||
|
||||
### Solution
|
||||
|
||||
Relatively simple, you just need follow the rules and not get too smart, otherwise
|
||||
it will get back at you.
|
||||
|
||||
## [Day 9: Rope Bridge](https://adventofcode.com/2022/day/9)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
We get a rope with knots and we want to track how many different positions are
|
||||
visited with the rope's tail.
|
||||
|
||||
:::
|
||||
|
||||
By this day, I have come to a conclusion that current skeleton for each day
|
||||
generates a lot of boilerplate. And even though it can be easily copied, it's
|
||||
just a waste of space and unnecessary code. Let's „simplify“ this (on one end
|
||||
while creating monster on the other end). I've gone through what we need in the
|
||||
preparations for the AoC. Let's sum up our requirements:
|
||||
- parsing
|
||||
- part 1 & 2
|
||||
- running on sample / input
|
||||
- tests
|
||||
|
||||
Parsing and implementation of both parts is code that changes each day and we
|
||||
cannot do anything about it. However running and testing can be simplified!
|
||||
|
||||
Let's introduce and export a new module `solution` that will take care of all of
|
||||
this. We will start by introducing a trait for each day.
|
||||
```rust
|
||||
pub trait Solution<Input, Output: Display> {
|
||||
fn parse_input<P: AsRef<Path>>(pathname: P) -> Input;
|
||||
|
||||
fn part_1(input: &Input) -> Output;
|
||||
fn part_2(input: &Input) -> Output;
|
||||
}
|
||||
```
|
||||
|
||||
This does a lot of work for us already, we have defined a trait and for each day
|
||||
we will create a structure representing a specific day. That structure will also
|
||||
implement the `Solution` trait.
|
||||
|
||||
Now we need to get rid of the boilerplate, we can't get rid of the `main` function,
|
||||
but we can at least move out the functionality.
|
||||
```rust
|
||||
fn run(type_of_input: &str) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.with_target(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.without_time()
|
||||
.compact()
|
||||
.init();
|
||||
color_eyre::install()?;
|
||||
|
||||
let input = Self::parse_input(format!("{}s/{}.txt", type_of_input, Self::day()));
|
||||
|
||||
info!("Part 1: {}", Self::part_1(&input));
|
||||
info!("Part 2: {}", Self::part_2(&input));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self::run("input")
|
||||
}
|
||||
```
|
||||
|
||||
This is all part of the `Solution` trait, which can implement methods while being
|
||||
dependant on what is provided by the implementing types. In this case, we just
|
||||
need to bound the `Output` type to implement `Display` that is necessary for the
|
||||
`info!` and format string there.
|
||||
|
||||
Now we can get to first of the nasty things we are going to do… And it is the
|
||||
`day()` method that you can see being used when constructing path to the input
|
||||
file. That method will generate a name of the file, e.g. `day01` and we know that
|
||||
we can _somehow_ deduce it from the structure name, given we name it reasonably.
|
||||
|
||||
```rust
|
||||
fn day() -> String {
|
||||
let mut day = String::from(type_name::<Self>().split("::").next().unwrap());
|
||||
day.make_ascii_lowercase();
|
||||
|
||||
day.to_string()
|
||||
}
|
||||
```
|
||||
|
||||
:::caution `type_name`
|
||||
|
||||
This feature is still experimental and considered to be internal, it is not
|
||||
advised to use it any production code.
|
||||
|
||||
:::
|
||||
|
||||
And now we can get to the nastiest stuff :weary: We will **generate** the tests!
|
||||
|
||||
We want to be able to generate tests for sample input in a following way:
|
||||
```rust
|
||||
test_sample!(day_01, Day01, 42, 69);
|
||||
```
|
||||
|
||||
There's not much we can do, so we will write a macro to generate the tests for us.
|
||||
|
||||
```rust
|
||||
#[macro_export]
|
||||
macro_rules! test_sample {
|
||||
($mod_name:ident, $day_struct:tt, $part_1:expr, $part_2:expr) => {
|
||||
#[cfg(test)]
|
||||
mod $mod_name {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_part_1() {
|
||||
let sample =
|
||||
$day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));
|
||||
assert_eq!($day_struct::part_1(&sample), $part_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_part_2() {
|
||||
let sample =
|
||||
$day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));
|
||||
assert_eq!($day_struct::part_2(&sample), $part_2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
We have used it in a similar way as macros in C/C++, one of the things that we
|
||||
can use to our advantage is defining „type“ of the parameters for the macro. All
|
||||
parameters have their name prefixed with `$` sign and you can define various „forms“
|
||||
of your macro. Let's go through it!
|
||||
|
||||
We have following parameters:
|
||||
- `$mod_name` which represents the name for the module with tests, it is typed
|
||||
with `ident` which means that we want a valid identifier to be passed in.
|
||||
- `$day_struct` represents the structure that will be used for tests, it is typed
|
||||
with `tt` which represents a _token tree_, in our case it is a type.
|
||||
- `$part_X` represents the expected output for the `X`th part and is of type `expr`
|
||||
which literally means an _expression_.
|
||||
|
||||
Apart from that we need to use `#[macro_export]` to mark the macro as exported
|
||||
for usage outside of the module. Now our skeleton looks like:
|
||||
```rust
|
||||
use aoc_2022::*;
|
||||
|
||||
type Input = String;
|
||||
type Output = String;
|
||||
|
||||
struct DayXX;
|
||||
impl Solution<Input, Output> for DayXX {
|
||||
fn parse_input<P: AsRef<Path>>(pathname: P) -> Input {
|
||||
file_to_string(pathname)
|
||||
}
|
||||
|
||||
fn part_1(input: &Input) -> Output {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn part_2(input: &Input) -> Output {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// DayXX::run("sample")
|
||||
DayXX::main()
|
||||
}
|
||||
|
||||
// test_sample!(day_XX, DayXX, , );
|
||||
```
|
||||
|
||||
### Solution
|
||||
|
||||
Not much to talk about, it is relatively easy to simulate.
|
||||
|
||||
## [Day 10: Cathode-Ray Tube](https://adventofcode.com/2022/day/10)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
Emulating basic arithmetic operations on a CPU and drawing on CRT based on the
|
||||
CPU's accumulator.
|
||||
|
||||
:::
|
||||
|
||||
In this day I have discovered an issue with my design of the `Solution` trait.
|
||||
And the issue is caused by different types of `Output` for the part 1 and part 2.
|
||||
|
||||
Problem is relatively simple and consists of simulating a CPU, I have approached
|
||||
it in a following way:
|
||||
```rust
|
||||
fn evaluate_instructions(instructions: &[Instruction], mut out: Output) -> Output {
|
||||
instructions
|
||||
.iter()
|
||||
.fold(State::new(), |state, instruction| {
|
||||
state.execute(instruction, &mut out)
|
||||
});
|
||||
|
||||
out
|
||||
}
|
||||
```
|
||||
|
||||
We just take the instructions, we have some state of the CPU and we execute the
|
||||
instructions one-by-one. Perfect usage of the `fold` (or `reduce` as you may know
|
||||
it from other languages).
|
||||
|
||||
You can also see that we have an `Output` type, so the question is how can we fix
|
||||
that problem. And the answer is very simple and _functional_. Rust allows you to
|
||||
have an `enumeration` that can _bear_ some other values apart from the type itself.
|
||||
|
||||
:::tip
|
||||
|
||||
We could've seen something like this with the `Result<T, E>` type that can be
|
||||
defined as
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E)
|
||||
}
|
||||
```
|
||||
|
||||
###### What does that mean though?
|
||||
|
||||
When we have an `Ok` value, it has the result itself, and when we get an `Err`
|
||||
value, it has the error. This also allows us to handle _results_ in a rather
|
||||
pretty way:
|
||||
```rust
|
||||
match do_something(x) {
|
||||
Ok(y) => {
|
||||
println!("SUCCESS: {}", y);
|
||||
},
|
||||
Err(y) => {
|
||||
eprintln!("ERROR: {}", y);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
My solution has a following outline:
|
||||
```rust
|
||||
fn execute(&self, i: &Instruction, output: &mut Output) -> State {
|
||||
// execute the instruction
|
||||
|
||||
// collect results if necessary
|
||||
match output {
|
||||
Output::Part1(x) => self.execute_part_1(y, x),
|
||||
Output::Part2(x) => self.execute_part_2(y, x),
|
||||
}
|
||||
|
||||
// return the obtained state
|
||||
new_state
|
||||
}
|
||||
```
|
||||
|
||||
You might think that it's a perfectly reasonable thing to do. Yes, **but** notice
|
||||
that the `match` statement doesn't _collect_ the changes in any way and also we
|
||||
pass `output` by `&mut`, so it is shared across each _iteration_ of the `fold`.
|
||||
|
||||
The dirty and ingenious thing is that `x`s are passed by `&mut` too and therefore
|
||||
they are directly modified by the helper functions. To sum it up and let it sit
|
||||
|
||||
> We are **collecting** the result **into** an **enumeration** that is **shared**
|
||||
> across **all** iterations of `fold`.
|
||||
|
||||
### Solution
|
||||
|
||||
Similar to _Day 9_, but there are some technical details that can get you.
|
||||
|
||||
## [Day 11: Monkey in the Middle](https://adventofcode.com/2022/day/11)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
Simulation of monkeys throwing stuff around and measuring your stress levels
|
||||
while your stuff is being passed around.
|
||||
|
||||
:::
|
||||
|
||||
I think I decided to use regular expressions here for the first time, cause
|
||||
parsing the input was a pain.
|
||||
|
||||
Also I didn't expect to implement Euclidean algorithm in Rust…
|
||||
|
||||
### Solution
|
||||
|
||||
Again, we're just running a simulation. Though I must admit it was very easy to
|
||||
make a small technical mistakes that could affect the final results very late.
|
||||
|
||||
## [Day 12: Hill Climbing Algorithm](https://adventofcode.com/2022/day/12)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
Finding shortest path up the hill and also shortest path down to the ground while
|
||||
also rolling down the hill…
|
||||
|
||||
:::
|
||||
|
||||
As I have said in the _tl;dr_, we are looking for the shortest path, but the start
|
||||
and goal differ for the part 1 and 2. So I have decided to refactor my solution
|
||||
to a BFS algorithm that takes neccessary parameters via functions:
|
||||
```rust
|
||||
fn bfs<F, G>(
|
||||
graph: &[Vec<char>], start: &Position, has_edge: F, is_target: G
|
||||
) -> Option<usize>
|
||||
where
|
||||
F: Fn(&[Vec<char>], &Position, &Position) -> bool,
|
||||
G: Fn(&[Vec<char>], &Position) -> bool
|
||||
```
|
||||
|
||||
We pass the initial vertex from the caller and everything else is left to the BFS
|
||||
algorithm, based on the `has_edge` and `is_target` functions.
|
||||
|
||||
This was easy! And that is not very usual in Rust once you want to pass around
|
||||
functions. :eyes:
|
||||
|
||||
### Solution
|
||||
|
||||
Looking for the shortest path… Must be Dijkstra, right? **Nope!** Half of the
|
||||
Reddit got jebaited though. In all fairness, nothing stops you from implementing
|
||||
the Dijkstra's algorithm for finding the shortest path, **but** if you know that
|
||||
all connected vertices are in a unit (actually $d = 1$) distance from each other,
|
||||
then you know that running Dijkstra is equivalent to running BFS, only with worse
|
||||
time complexity, because of the priority heap instead of the queue.
|
||||
|
||||
## [Day 13: Distress Signal](https://adventofcode.com/2022/day/13)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
Processing packets with structured data from the distress signal.
|
||||
|
||||
:::
|
||||
|
||||
You can implement a lot of traits if you want to. It is _imperative_ to implement
|
||||
ordering on the packets. I had a typo, so I also proceeded to implement a `Display`
|
||||
trait for debugging purposes:
|
||||
```rust
|
||||
impl Display for Packet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Packet::Integer(x) => write!(f, "{x}"),
|
||||
Packet::List(lst) => write!(f, "[{}]", lst.iter().map(|p| format!("{p}")).join(",")),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution
|
||||
|
||||
A lot of technical details… Parsing is nasty too…
|
||||
|
||||
## [Day 14: Regolith Reservoir](https://adventofcode.com/2022/day/14)
|
||||
|
||||
:::info tl;dr
|
||||
|
||||
Let's simulate falling sand grain-by-grain.
|
||||
|
||||
:::
|
||||
|
||||
Again, both parts are relatively similar with minimal changes, so it is a good
|
||||
idea to refactor it a bit. Similar approach to the [BFS above]. Also this is the
|
||||
first day where I ran into efficiency issues and had to redo my solution to speed
|
||||
it up just a bit.
|
||||
|
||||
### Solution
|
||||
|
||||
Tedious.
|
||||
|
||||
[_Advent of Code_]: https://adventofcode.com
|
||||
[BFS above]: #day-12-hill-climbing-algorithm
|
Loading…
Reference in a new issue