This commit is contained in:
Markus Brueckner 2024-12-08 10:45:26 +01:00
parent ce9038a90e
commit e42e14f215
4 changed files with 178 additions and 0 deletions

7
2024/day8/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "day8"
version = "0.1.0"

6
2024/day8/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "day8"
version = "0.1.0"
edition = "2021"
[dependencies]

51
2024/day8/README.md Normal file
View file

@ -0,0 +1,51 @@
# Solution Day 8
A bit of geometry for a change. The task was quite ambiguous, I felt. The text says:
> In particular, an antinode occurs at any point that is perfectly in line with two antennas of the same frequency - but only when one of the antennas is twice as far away as the other
and then goes on to give exambles, where the antinodes lie outside the two antennas, like this:
```
..#....a....a....#....
```
Either I'm not getting the exact meaning of "antinode" (English is not my native language) or there's another set of antinodes _between_ the two antennas:
```
..#....a.##.a....#....
```
Those should also fulfill the requirement of one antenna being twice as far away as the other.
Anyway, going with the examples, this is basically just calculating an equation of the form
`p = mn + b`, with `p`, and `b` being points in 2D space, `m = a - b` (the vector between
antennas `a` and `b`), and `n` being in the natural numbers **N**.
Implementing this as infrastructure (along with loading and parsing the data), makes the actual tasks rather simple.
## Task 1
The main point in this task is to only include points of order 1 formed by 2 different antennas, hence
why the inner nested loop in `get_antinodes` has to always start one index _above_ the current outer loop,
to never pair an antenna with itself (I got this wrong in the first try). Those nested loops will form all possible pairs
of the antennas:
```
[1,2,3] => [[1,2], [1,3], [2,3]]
```
## Task 2
The change to the code are actually miniscule: antennas can now interfere with themselves (presumably if there's
another antenna of the same frequency somewhere on the map. No idea how this makes physical sense, but there you are...) and
we need to continue our antinodes beyond order 1 until we're outside the map.
Since we already had a "outside the map"-filter in place from task 1, the lazy solution was to calculate the antinodes
up to an order, that is _definitely_ outside the map in order to capture all possible nodes (hence why calculating the the 50th
node, even if that could never be on the map).
This also pairs antennas with themselves, so the inner loop in `get_antinodes` now starts at the index of the outer node.
```
[1,2,3] => [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]
```

114
2024/day8/src/main.rs Normal file
View file

@ -0,0 +1,114 @@
use std::collections::{HashMap, HashSet};
struct Antenna {
x: usize,
y: usize,
marker: char,
}
struct AntennaMap {
width: usize,
height: usize,
antennas: Vec<Antenna>,
}
#[derive(Hash, PartialEq, Eq)]
struct Antinode {
x: usize,
y: usize,
}
fn load_input() -> AntennaMap {
let input = std::fs::read_to_string("./input.txt").expect("Should be able to read input file");
let lines: Vec<&str> = input.lines().collect();
let antennas: Vec<Antenna> = lines
.iter()
.enumerate()
.flat_map(|(y, line)| {
line.chars().enumerate().filter_map(move |(x, ch)| {
if ch != '.' {
Some(Antenna { x, y, marker: ch })
} else {
None
}
})
})
.collect();
AntennaMap {
width: lines.iter().next().unwrap().len(),
height: lines.len(),
antennas,
}
}
fn group_antennas(antennas: &Vec<Antenna>) -> HashMap<char, Vec<&Antenna>> {
let mut map = HashMap::new();
for antenna in antennas.into_iter() {
if !map.contains_key(&antenna.marker) {
map.insert(antenna.marker, vec![]);
}
map.get_mut(&antenna.marker).unwrap().push(antenna);
}
map
}
fn get_nth_antinode(antenna_a: &Antenna, antenna_b: &Antenna, n: usize) -> Option<Antinode> {
if (n + 1) * antenna_a.x >= n * antenna_b.x && (n + 1) * antenna_a.y >= n * antenna_b.y {
Some(Antinode {
x: (n + 1) * antenna_a.x - n * antenna_b.x,
y: (n + 1) * antenna_a.y - n * antenna_b.y,
})
} else {
None
}
}
fn get_antinodes(
antennas: &Vec<&Antenna>,
width: usize,
height: usize,
max_n: usize,
include_self: bool,
) -> Vec<Antinode> {
let mut antinodes = vec![];
for (idx, antenna_a) in antennas.iter().enumerate() {
for antenna_b in antennas[idx + (if include_self { 0 } else { 1 })..].iter() {
for n in 1..=max_n {
antinodes.push(get_nth_antinode(antenna_a, antenna_b, n));
antinodes.push(get_nth_antinode(antenna_b, antenna_a, n));
}
}
}
antinodes
.into_iter()
.filter_map(|node_opt| node_opt)
.filter(|node| node.x < width && node.y < height)
.collect()
}
fn task1() {
let antenna_map = load_input();
let by_frequency = group_antennas(&antenna_map.antennas);
let all_antinodes = by_frequency
.iter()
.flat_map(|entry| get_antinodes(entry.1, antenna_map.width, antenna_map.height, 1, false));
let unique_antinodes: HashSet<Antinode> = all_antinodes.collect();
println!("Task 1: Unique nodes: {}", unique_antinodes.len());
}
fn task2() {
let antenna_map = load_input();
let by_frequency = group_antennas(&antenna_map.antennas);
let all_antinodes = by_frequency
.iter()
.flat_map(|entry| get_antinodes(entry.1, antenna_map.width, antenna_map.height, 50, true)); // arbitrarily high n to make sure we get everything inside the map
let unique_antinodes: HashSet<Antinode> = all_antinodes.collect();
println!("Task 2: Unique nodes: {}", unique_antinodes.len());
}
fn main() {
task1();
task2();
}