Day 8: Resonant Collinearity
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
Rust
For the first time, I can post my solution, because I actually solved it on the day :D Probably not the cleanest or optimal solution, but it does solve the problem.
Very long, looking forward to someone solving it in 5 lines of unicode :D
#[cfg(test)] mod tests { fn get_frequences(input: &str) -> Vec { let mut freq = vec![]; for char in input.chars() { if char == '.' { continue; } if !freq.contains(&char) { freq.push(char); } } freq } fn find_antennas(board: &Vec>, freq: char) -> Vec<(isize, isize)> { let mut antennas = vec![]; for (i, line) in board.iter().enumerate() { for (j, char) in line.iter().enumerate() { if *char == freq { antennas.push((i as isize, j as isize)); } } } antennas } fn calc_antinodes(first: &(isize, isize), second: &(isize, isize)) -> Vec<(isize, isize)> { let deltax = second.0 - first.0; let deltay = second.1 - first.1; if deltax == 0 && deltay == 0 { return vec![]; } vec![ (first.0 - deltax, first.1 - deltay), (second.0 + deltax, second.1 + deltay), ] } #[test] fn test_calc_antinodes() { let expected = vec![(0, -1), (0, 2)]; let actual = calc_antinodes(&(0, 0), &(0, 1)); for i in &expected { assert!(actual.contains(i)); } let actual = calc_antinodes(&(0, 1), &(0, 0)); for i in &expected { assert!(actual.contains(i)); } } fn calc_all_antinodes(board: &Vec>, freq: char) -> Vec<(isize, isize)> { let antennas = find_antennas(&board, freq); let mut antinodes = vec![]; for (i, first) in antennas.iter().enumerate() { for second in antennas[i..].iter() { antinodes.extend(calc_antinodes(first, second)); } } antinodes } fn prune_nodes( nodes: &Vec<(isize, isize)>, height: isize, width: isize, ) -> Vec<(isize, isize)> { let mut pruned = vec![]; for node in nodes { if pruned.contains(node) { continue; } if node.0 < 0 || node.0 >= height { continue; } if node.1 < 0 || node.1 >= width { continue; } pruned.push(node.clone()); } pruned } fn print_board(board: &Vec>, pruned: &Vec<(isize, isize)>) { for (i, line) in board.iter().enumerate() { for (j, char) in line.iter().enumerate() { if pruned.contains(&(i as isize, j as isize)) { print!("#"); } else { print!("{char}"); } } println!(); } } #[test] fn day8_part1_test() { let input: String = std::fs::read_to_string("src/input/day_8.txt").unwrap(); let frequencies = get_frequences(&input); let board = input .trim() .split('\n') .map(|line| line.chars().collect::>()) .collect::>>(); let mut all_nodes = vec![]; for freq in frequencies { let nodes = calc_all_antinodes(&board, freq); all_nodes.extend(nodes); } let height = board.len() as isize; let width = board[0].len() as isize; let pruned = prune_nodes(&all_nodes, height, width); println!("{:?}", pruned); print_board(&board, &pruned); println!("{}", pruned.len()); // 14 in test } fn calc_antinodes2(first: &(isize, isize), second: &(isize, isize)) -> Vec<(isize, isize)> { let deltax = second.0 - first.0; let deltay = second.1 - first.1; if deltax == 0 && deltay == 0 { return vec![]; } let mut nodes = vec![]; for n in 0..50 { nodes.push((first.0 - deltax * n, first.1 - deltay * n)); nodes.push((second.0 + deltax * n, second.1 + deltay * n)); } nodes } fn calc_all_antinodes2(board: &Vec>, freq: char) -> Vec<(isize, isize)> { let antennas = find_antennas(&board, freq); let mut antinodes = vec![]; for (i, first) in antennas.iter().enumerate() { for second in antennas[i..].iter() { antinodes.extend(calc_antinodes2(first, second)); } } antinodes } #[test] fn day8_part2_test() { let input: String = std::fs::read_to_string("src/input/day_8.txt").unwrap(); let frequencies = get_frequences(&input); let board = input .trim() .split('\n') .map(|line| line.chars().collect::>()) .collect::>>(); let mut all_nodes = vec![]; for freq in frequencies { let nodes = calc_all_antinodes2(&board, freq); all_nodes.extend(nodes); } let height = board.len() as isize; let width = board[0].len() as isize; let pruned = prune_nodes(&all_nodes, height, width); println!("{:?}", pruned); print_board(&board, &pruned); println!("{}", pruned.len()); } }deleted by creator
C#
public class Day08 : Solver { private ImmutableArray data; private int width, height; public void Presolve(string input) { data = input.Trim().Split("\n").ToImmutableArray(); width = data[0].Length; height = data.Length; } public string SolveFirst() { Dictionary> antennae = []; HashSet<(int, int)> antinodes = []; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if ('.' == data[j][i]) continue; antennae.TryAdd(data[j][i], []); foreach (var (oi, oj) in antennae[data[j][i]]) { int di = i - oi; int dj = j - oj; int ai = i + di; int aj = j + dj; if (ai >= 0 && aj >= 0 && ai < width && aj < height) { antinodes.Add((ai, aj)); } ai = oi - di; aj = oj - dj; if (ai >= 0 && aj >= 0 && ai < width && aj < height) { antinodes.Add((ai, aj)); } } antennae[data[j][i]].Add((i, j)); } } return antinodes.Count.ToString(); } public string SolveSecond() { Dictionary> antennae = []; HashSet<(int, int)> antinodes = []; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if ('.' == data[j][i]) continue; antennae.TryAdd(data[j][i], []); foreach (var (oi, oj) in antennae[data[j][i]]) { int di = i - oi; int dj = j - oj; for (int ai = i, aj = j; ai >= 0 && aj >= 0 && ai < width && aj < height; ai += di, aj +=dj) { antinodes.Add((ai, aj)); } for (int ai = oi, aj = oj; ai >= 0 && aj >= 0 && ai < width && aj < height; ai -= di, aj -=dj) { antinodes.Add((ai, aj)); } } antennae[data[j][i]].Add((i, j)); } } return antinodes.Count.ToString(); } }Haskell
Not a very pretty solution today, I’m afraid.
import Control.Arrow import Control.Monad import Data.Biapplicative import Data.Ix import Data.Map (Map) import Data.Map qualified as Map import Data.Set qualified as Set type Coords = (Int, Int) readInput :: String -> Map Coords Char readInput s = Map.fromAscList [ ((i, j), c) | (i, l) <- zip [0 ..] (lines s), (j, c) <- zip [0 ..] l ] (.+.), (.-.) :: Coords -> Coords -> Coords (.+.) = join biliftA2 (+) (.-.) = join biliftA2 (-) part1, part2 :: (Coords -> Bool) -> (Coords, Coords) -> [Coords] part1 valid (p1, p2) = let s = p2 .-. p1 in filter valid [p1 .-. s, p2 .+. s] part2 valid (p1, p2) = let (si, sj) = p2 .-. p1 d = gcd si sj s = (si `div` d, sj `div` d) in takeWhile valid (iterate (.+. s) p1) ++ takeWhile valid (drop 1 $ iterate (.-. s) p2) pairs (x : xs) = map (x,) xs ++ pairs xs pairs _ = [] main = do input <- readInput <$> readFile "input08" let antennas = Map.filter (/= '.') input antennaGroups = Map.foldrWithKey (\p c m -> Map.insertWith (++) c [p] m) Map.empty antennas valid = inRange . (Set.findMin &&& Set.findMax) $ Map.keysSet input antiNodes model = Set.fromList . concatMap (concatMap (model valid) . pairs) $ antennaGroups print . Set.size $ antiNodes part1 print . Set.size $ antiNodes part2Lisp
Could probably just write points right to the results instead of to an intermediate list, but it runs instantly, so my motivation to do so was low.
Code
(defun p1-process-line (line) (to-symbols line 'advt2024-d8)) (defun count-results (results) (loop for i from 0 below (array-total-size results) count (row-major-aref results i))) (defun place-annode (pos results) (let ((x (first pos)) (y (second pos))) (when (in-map results x y) (setf (aref results y x) t)))) (defun create-annodes-p1 (x1 y1 x2 y2) (let ((delta-x (- x2 x1)) (delta-y (- y2 y1))) (list (list (- x1 delta-x) (- y1 delta-y)) (list (+ x2 delta-x) (+ y2 delta-y))))) (defun place-annodes (positions results create-annodes) (when positions (loop with a = (car positions) with x1 = (first a) with y1 = (second a) for b in (cdr positions) for ans = (funcall create-annodes x1 y1 (first b) (second b)) do (dolist (a ans) (place-annode a results))) (place-annodes (cdr positions) results create-annodes))) (defun place-all-annodes (xmits map &optional (create-annodes #'create-annodes-p1)) (let ((results (make-array (array-dimensions map) :element-type 'boolean :initial-element nil))) (loop for k being the hash-key of xmits do (place-annodes (gethash k xmits) results create-annodes)) results)) (defun find-transmitters (map) "look throught the map and record where the transmitters are in a hash map" (let ((h (make-hash-table))) (destructuring-bind (rows cols) (array-dimensions map) (loop for j from 0 below rows do (loop for i from 0 below cols for v = (aref map j i) unless (eql v '|.|) do (push (list i j) (gethash v h)) ))) h)) (defun run-p1 (file) (let* ((map (list-to-2d-array (read-file file #'p1-process-line)))) (count-results (place-all-annodes (find-transmitters map) map)) )) (defun create-annodes-2 (x1 y1 x2 y2 map) (destructuring-bind (rows cols) (array-dimensions map) (let* ((m (/ (- y2 y1) (- x2 x1) )) (b (- y2 (* m x2)))) (loop for x from 0 below cols for y = (+ b (* x m)) for r = (nth-value 1 (floor y)) when (and (= r 0) (>= y 0) (< y rows)) collect (list x y))))) (defun run-p2 (file) (let* ((map (list-to-2d-array (read-file file #'p1-process-line)))) (count-results (place-all-annodes (find-transmitters map) map (lambda (x1 y1 x2 y2) (create-annodes-2 x1 y1 x2 y2 map))))))Nim
Overall really simple puzzle, but description is so confusing, that I mostly solved it based on example diagrams.
Edit: much shorter and faster one-pass solution. Runtime: 132 ustype Vec2 = tuple[x,y: int] func delta(a, b: Vec2): Vec2 = (a.x-b.x, a.y-b.y) func outOfBounds[T: openarray | string](pos: Vec2, grid: seq[T]): bool = pos.x < 0 or pos.y < 0 or pos.x > grid[0].high or pos.y > grid.high proc solve(input: string): AOCSolution[int, int] = var grid = input.splitLines() var antennas: Table[char, seq[Vec2]] for y, line in grid: for x, c in line: if c != '.': discard antennas.hasKeyOrPut(c, newSeq[Vec2]()) antennas[c].add (x, y) var antinodesP1: HashSet[Vec2] var antinodesP2: HashSet[Vec2] for _, list in antennas: for ind, ant1 in list: antinodesP2.incl ant1 # each antenna is antinode for ant2 in list.toOpenArray(ind+1, list.high): let d = delta(ant1, ant2) for dir in [-1, 1]: var i = dir while true: let antinode = (x: ant1.x+d.x*i, y: ant1.y+d.y*i) if antinode.outOfBounds(grid): break if i in [1, -2]: antinodesP1.incl antinode antinodesP2.incl antinode i += dir result.part1 = antinodesP1.len result.part2 = antinodesP2.len
Rust
use std::collections::{HashMap, HashSet}; use crate::solver::DaySolver; use crate::grid::{Coordinate, Grid}; fn add_distance(coordinate: Coordinate, distance: (i64, i64)) -> Option { coordinate.try_add(distance) } fn sub_distance(coordinate: Coordinate, distance: (i64, i64)) -> Option { coordinate.try_sub(distance) } fn part2_possible_antinodes( grid: &Grid>, coordinate: Coordinate, distance: (i64, i64), op: F, mut accumulator: Vec ) -> Vec where F: Fn(Coordinate, (i64, i64)) -> Option { match op(coordinate, distance).filter(|c| grid.get(*c).is_some()) { None => accumulator, Some(next_coord) => { accumulator.push(next_coord); part2_possible_antinodes(grid, next_coord, distance, op, accumulator) } } } trait Pairable { fn pairs(&self) -> Vec<(&T, &T)>; } impl Pairable for HashSet { fn pairs(&self) -> Vec<(&T, &T)> { let v: Vec<&T> = self.iter().collect(); let mut p = vec![]; for i in 0..v.len() { let thing1 = v[i]; for thing2 in &v[i+1..] { p.push((thing1, *thing2)); } } p } } fn parse_input(input: String) -> (Grid>, HashMap>) { let g: Grid> = input.lines() .map(|line| line.chars() .map(|c| if c == '.' { None } else { Some(c) }).collect::>>() ) .collect::>>>() .into(); let mut freq_to_coords: HashMap> = HashMap::new(); for (coord, freq_opt) in g.iter() { match freq_opt { None => (), Some(freq) => { freq_to_coords.entry(*freq) .and_modify(|coords| { coords.insert(coord); }) .or_insert(HashSet::from([coord])); } } } (g, freq_to_coords) } pub struct Day08Solver; impl DaySolver for Day08Solver { fn part1(&self, input: String) -> usize { let (g, freq_to_coords) = parse_input(input); let mut antinodes: HashSet = HashSet::new(); for (_, coords) in freq_to_coords { // println!("Freq = {}", freq); for (c1, c2) in coords.pairs() { let distance = c1.xy_distance_to(c2); let possible_antinodes: Vec = [c1.try_sub(distance), c2.try_add(distance)].into_iter() .flat_map(|co| co.filter(|c| g.get(*c).is_some())) .collect(); // println!("Pair = ({},{}), antinodes = {:?}", c1, c2, possible_antinodes); for antinode in possible_antinodes { antinodes.insert(antinode); } } } antinodes.len() } fn part2(&self, input: String) -> usize { let (g, freq_to_coords) = parse_input(input); let mut antinodes: HashSet = HashSet::new(); for (freq, coords) in freq_to_coords { println!("Freq = {}", freq); for (c1, c2) in coords.pairs() { let distance = c1.xy_distance_to(c2); let possible_antinodes: Vec = [ part2_possible_antinodes(&g, *c1, distance, add_distance, vec![*c1]), part2_possible_antinodes(&g, *c1, distance, sub_distance, vec![*c1]), part2_possible_antinodes(&g, *c2, distance, add_distance, vec![*c2]), part2_possible_antinodes(&g, *c2, distance, sub_distance, vec![*c2]), ].into_iter().flatten().collect(); println!("Pair = ({},{}), antinodes = {:?}", c1, c2, possible_antinodes); for antinode in possible_antinodes { antinodes.insert(antinode); } } } antinodes.len() } }https://gitlab.com/bricka/advent-of-code-2024-rust/-/blob/main/src/days/day08.rs?ref_type=heads
Rust
Proper Point and Vector types made this pretty simple, part 2 was just a tiny change (basically
whileinstead ofif), but left with a lot of copy-pasted code.Solution
use euclid::default::*; const N_ANTENNAS: usize = (b'z' - b'0') as usize + 1; // For each frequency (from b'0' to b'z') the list of antenna positions type Antennas = Box<[Vec>]>; fn parse(input: String) -> (Antennas, Rect) { let mut antennas = vec![Vec::new(); N_ANTENNAS].into_boxed_slice(); let mut width = 0; let mut height = 0; for (y, l) in input.lines().enumerate() { height = y + 1; if width == 0 { width = l.len() } else { assert!(width == l.len()) } for (x, b) in l.bytes().enumerate().filter(|(_, b)| *b != b'.') { antennas[(b - b'0') as usize].push(Point2D::new(x, y).to_i32()) } } let bounds = Rect::new(Point2D::origin(), Size2D::new(width, height).to_i32()); (antennas, bounds) } fn part1(input: String) { let (antennas, bounds) = parse(input); let mut antinodes = vec![vec![false; bounds.width() as usize]; bounds.height() as usize]; for list in antennas.iter().filter(|l| !l.is_empty()) { for (i, &a) in list.iter().enumerate().skip(1) { for &b in list.iter().take(i) { let diff = b - a; let ax = a - diff; if bounds.contains(ax) { antinodes[ax.y as usize][ax.x as usize] = true; } let bx = b + diff; if bounds.contains(bx) { antinodes[bx.y as usize][bx.x as usize] = true; } } } } let sum = antinodes .iter() .map(|row| row.iter().map(|b| u32::from(*b)).sum::()) .sum::(); println!("{sum}"); } fn part2(input: String) { let (antennas, bounds) = parse(input); let mut antinodes = vec![vec![false; bounds.width() as usize]; bounds.height() as usize]; for list in antennas.iter().filter(|l| !l.is_empty()) { for (i, &a) in list.iter().enumerate().skip(1) { for &b in list.iter().take(i) { let diff = b - a; // Start at antenna a, keep going until hitting bounds let mut ax = a; while bounds.contains(ax) { antinodes[ax.y as usize][ax.x as usize] = true; ax -= diff; } let mut bx = b; while bounds.contains(bx) { antinodes[bx.y as usize][bx.x as usize] = true; bx += diff; } } } } let sum = antinodes .iter() .map(|row| row.iter().map(|b| u32::from(*b)).sum::()) .sum::(); println!("{sum}"); } util::aoc_main!();also on github
I also did it in Rust, though mine is quite a bit more verbose than yours :). https://gitlab.com/bricka/advent-of-code-2024-rust/-/blob/main/src/days/day08.rs?ref_type=heads
Have you considered using a
HashSet<(usize, usize)>to store the visited locations instead of a 2d vector?I try to use
Vecs instead ofHashSets and maps whenever the key domain is reasonably small (just the grid in this case), simply because in the end direct memory access is a lot faster than always hashing values.But looking at this case again, it is certainly a lot easier to have just
antinodes.len()at the end instead of counting all true values. This datastructure is also not really performance-critical, so aHashSetis probably the cleaner choice here.
J
J really doesn’t have hashes! Or anything like hashes! And it’s really annoying after a while!
What it does have is automatic internal optimization via hashing of the “index of” operation
m i. nwheremis a fixed list (the object being searched) andnis the query, which can vary. But as soon as you updatemthe hash table is thrown away. And you still have to choose some kind of numeric key, or store a list of boxed pairs where the first coordinate is the key – effectively this is an old-style Lisp association list, but with extra steps because you have to use boxing to defeat J’s automatic array concatenation and reshaping. If you want non-cubical shapes (J calls these “ragged arrays”), or heterogeneous lists, you end up writingu &.>a lot – this means “unbox, applyuthen rebox”. J arrays are required to be rectangular and homogeneous, but a boxed anything is a single atom just like a number is.It’s just a really bad choice of language if you want data structures other than essentially-cubical arrays. On the other hand, once you beat the list manipulation primitives into producing your 1970s Lisp data structure of choice, the rest of the program is as nice as it usually is.
data_file_name =: '8.data' grid =: ,. > cutopen fread data_file_name 'rsize csize' =: $ grid inbounds =: monad : '(*/ y >: 0 0) * (*/ y < rsize, csize)' antenna_types =: (#~ (~: & '.')) ~. , grid NB. list_antennas gives a list of boxed matrices of shape 2 n_k in cell k, where NB. n_k is the number of antennas of type k and the rows are coordinates of that type list_antennas =: monad define antenna_locs =. (# antenna_types) $ a: for_r. i. rsize do. for_c. i. csize do. cell =. y {~ <(r, c) if. '.' ~: cell do. at =. antenna_types i. cell antenna_locs =. ((<(r, c)) ,&.> at { antenna_locs) at} antenna_locs end. end. end. NB. _2 ]\ l reshapes l into length 2 rows without finding its length ahead of time (_2 & (]\))&.> antenna_locs ) NB. a1 pair_antinodes a2 gives the two antinodes from that pair pair_antinodes =: dyad : '(#~ inbounds"1) ((2 * x) - y) ,: (2 * y) - x' NB. if u is a symmetric dyad expecting rank 1 arguments, u on_pairs is a monad NB. expecting a list of rank 1 arguments, and yields the concatenation of x u y NB. where (x, y) is drawn from the (unordered) pairs of elements of the argument NB. see page_pairs in 5.ijs for a non-point-free version of pair enumeration on_pairs =: adverb define ; @: (< @: u/"2) @: ({~ (; @: (< @: (,~"0 i.)"0) @: i. @: #)) ) NB. antinodes antennas gives a list (may contain duplicates) of all the antinodes from NB. that set of antennas antinodes =: pair_antinodes on_pairs NB. on_antennas concatenates and uniquifies result lists from all antennas on_antennas =: adverb define ~. @: ; @: (u &.>) @: list_antennas ) result1 =: # antinodes on_antennas grid NB. a1 res_antinodes a2 gives the list of antinodes from that pair with resonance res_antinodes =: dyad define step =. (% +./) x - y NB. lazy: max_steps doesn't take location of x into account max_steps =. <. (rsize % 1 >. | 0 { step) <. (csize % 1 >. 1 { step) (#~ inbounds"1) x +"1 step *"1 0 i: max_steps ) result2 =: # res_antinodes on_pairs on_antennas gridKotlin
A bit late to the party, but here’s my solution. I don’t know, if you even need to search for the smallest integer vector in the same direction in part 2, but I did it anyway.
Code:
import kotlin.math.abs import kotlin.math.pow fun main() { fun part1(input: List): Int { val inputMap = Day08Map(input) return inputMap.isoFrequencyNodeVectorsByLocations .flatMap { (location, vectors) -> vectors.map { (2.0 scaleVec it) + location } } .toSet() .count { inputMap.isInGrid(it) } } fun part2(input: List): Int { val inputMap = Day08Map(input) return buildSet { inputMap.isoFrequencyNodeVectorsByLocations.forEach { (location, vectors) -> vectors.forEach { vector -> var i = 0.0 val scaledDownVector = smallestIntegerVectorInSameDirection2D(vector) while (inputMap.isInGrid(location + (i scaleVec scaledDownVector))) { add(location + (i scaleVec scaledDownVector)) i++ } } } }.count() } val testInput = readInput("Day08_test") check(part1(testInput) == 14) check(part2(testInput) == 34) val input = readInput("Day08") part1(input).println() part2(input).println() } tailrec fun gcdEuclid(a: Int, b: Int): Int = if (b == 0) a else if (a == 0) b else if (a > b) gcdEuclid(a - b, b) else gcdEuclid(a, b - a) fun smallestIntegerVectorInSameDirection2D(vec: VecNReal): VecNReal { assert(vec.dimension == 2) // Only works in two dimensions. assert(vec == vec.roundComponents()) // Only works on integer vectors. return (gcdEuclid(abs(vec[0].toInt()), abs(vec[1].toInt())).toDouble().pow(-1) scaleVec vec).roundComponents() } class Day08Map(input: List): Grid2D(input.reversed().map { it.toList() }) { init { transpose() } val isoFrequencyNodesLocations = asIterable().toSet().filter { it != '.' }.map { frequency -> asIterable().indicesWhere { frequency == it } } val isoFrequencyNodeVectorsByLocations = buildMap { isoFrequencyNodesLocations.forEach { isoFrequencyLocationList -> isoFrequencyLocationList.mapIndexed { index, nodeLocation -> this[VecNReal(nodeLocation)] = isoFrequencyLocationList .slice((0 until index) + ((index + 1)..isoFrequencyLocationList.lastIndex)) .map { VecNReal(it) - VecNReal(nodeLocation) } } } } }deleted by creator
C
Not hard but a little fiddly.
Code
#include "common.h" #define GZ 52 static char g[GZ][GZ]; #define ANTI_P1 1 #define ANTI_P2 2 static uint8_t anti[GZ][GZ]; static int w,h; int main(int argc, char **argv) { int p1=0,p2=0, x,y, x1,y1, ax,ay, i; char *lf; if (argc > 1) DISCARD(freopen(argv[1], "r", stdin)); for (h=0; hw || (ay = y-(y1-y)*i) <0 || ay>h) break; anti[ay][ax] |= ANTI_P1 * i==1; anti[ay][ax] |= ANTI_P2; } for (i=0; ; i++) { if ((ax = x1+(x1-x)*i) <0 || ax>w || (ay = y1+(y1-y)*i) <0 || ay>h) break; anti[ay][ax] |= ANTI_P1 * i==1; anti[ay][ax] |= ANTI_P2; } } } for (y=0; yAnd I of course misread and wasted a bunch of time debugging the second part, entirely missed the fact that antinodes occurred on top of the emanating antennae as well…
C#
public static class LINQExt { public static IEnumerable<(T,T)> PermutatePairs(this IEnumerable source) { return source.SelectMany(k => source.Where(v => !v?.Equals(k) ?? false).Select(v => (k, v))); } } struct Antenna { public int X, Y; public char Frequency; } List antennae = new List(); int width, height; public void Input(IEnumerable lines) { char[] map = string.Join("", lines).ToCharArray(); width = lines.First().Length; height = lines.Count(); for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { char at = map[y * width + x]; if (at == '.') continue; antennae.Add(new Antenna{ X = x, Y = y, Frequency = at }); } } public void Part1() { HashSet<(int, int)> antinodes = new HashSet<(int, int)>(); foreach (var antinode in antennae.GroupBy(k => k.Frequency).SelectMany(g => g.PermutatePairs()).SelectMany(v => GetOpposing(v.Item1, v.Item2)).Where(InRange)) antinodes.Add(antinode); Console.WriteLine($"Unique antinodes: {antinodes.Count}"); } public void Part2() { HashSet<(int, int)> antinodes = new HashSet<(int, int)>(); foreach (var antennaePair in antennae.GroupBy(k => k.Frequency).SelectMany(g => g.PermutatePairs())) { // Iterate separately, to make the handling of bound exit easier foreach (var antinode in GetAllOpposing(antennaePair.Item1, antennaePair.Item2).TakeWhile(InRange)) antinodes.Add(antinode); foreach (var antinode in GetAllOpposing(antennaePair.Item2, antennaePair.Item1).TakeWhile(InRange)) antinodes.Add(antinode); } Console.WriteLine($"Unique antinodes: {antinodes.Count}"); } bool InRange((int, int) point) { return point.Item1 >= 0 && point.Item1 < width && point.Item2 >= 0 && point.Item2 < height; } (int, int)[] GetOpposing(Antenna a, Antenna b) { return new[] { (a.X + (a.X - b.X), a.Y + (a.Y - b.Y)), (b.X + (b.X - a.X), b.Y + (b.Y - a.Y)) }; } IEnumerable<(int, int)> GetAllOpposing(Antenna a, Antenna b) { (int, int) diff = (a.X - b.X, a.Y - b.Y); (int, int) at = (a.X, a.Y); yield return at; while (true) { at.Item1 += diff.Item1; at.Item2 += diff.Item2; yield return at; } }Rust
Pretty happy with my solution today. I took my time today as it was a bit of a slow day and did it in Rust instead of python. Having proper Vec2 types is very nice.
Tap for spoiler
use std::{collections::HashMap, error::Error, io::Read}; use glam::{IVec2, Vec2}; fn permutations_of_size_two(antennas: &[Vec2]) -> Vec<[&Vec2; 2]> { let mut permutations = vec![]; for (i, antenna) in antennas.iter().enumerate() { for j in 0..antennas.len() { if i == j { continue; } permutations.push([antenna, &antennas[j]]) } } permutations } fn main() -> Result<(), Box> { let mut input = String::new(); std::io::stdin().read_to_string(&mut input)?; let height = input.lines().count() as i32; let width = input.lines().next().unwrap().len() as i32; let antenna_positions = input .lines() .enumerate() .flat_map(|(y, l)| l.chars().enumerate().map(move |(x, c)| (Vec2::new(x as f32, y as f32), c)) ) .filter(|(_v, c)| *c != '.') .fold(HashMap::new(), |mut acc: HashMap> , current| { acc.entry(current.1).or_default().push(current.0); acc }); let mut antinodes = vec![]; for (_c, antennas) in antenna_positions { let perms = permutations_of_size_two(&antennas); for [first, second] in perms { let mut i = 1.; loop { let antinode = (first + (second-first) * i).round(); if (0..height).contains(&(antinode.x as i32)) && (0..width).contains(&(antinode.y as i32)) { antinodes.push(antinode); } else { break; } i += 1.; } } } let mut antinode_count = 0; let map = input .lines() .enumerate() .map(|(y, l)| l.chars().enumerate().map(|(x, c)| { if antinodes.contains(&Vec2::new(x as f32, y as f32)) { println!("({x},{y})"); antinode_count += 1; return '#'; } c }).collect::() ) .collect::>() .join("\n"); println!("{map}"); println!("{antinode_count}"); Ok(()) }





