- day 8
This commit is contained in:
parent
ce9038a90e
commit
e42e14f215
4 changed files with 178 additions and 0 deletions
7
2024/day8/Cargo.lock
generated
Normal file
7
2024/day8/Cargo.lock
generated
Normal 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
6
2024/day8/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "day8"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
51
2024/day8/README.md
Normal file
51
2024/day8/README.md
Normal 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
114
2024/day8/src/main.rs
Normal 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();
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue