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
C#
public class Day08 : Solver
{
private ImmutableArray<string> 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<char, List<(int, int)>> 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<char, List<(int, int)>> 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();
}
}
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<char> {
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<Vec<char>>, 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<Vec<char>>, 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<Vec<char>>, 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::<Vec<char>>())
.collect::<Vec<Vec<char>>>();
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<Vec<char>>, 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::<Vec<char>>())
.collect::<Vec<Vec<char>>>();
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());
}
}
Haskell
I overslept 26 minutes (AoC starts at 06:00 here) which upsets me more than it should.
I thought this one was going to be hard on performance or memory but it was surprisingly easy.
import Control.Arrow hiding (first, second)
import Data.Bifunctor
import Data.Array.Unboxed (UArray)
import qualified Data.List as List
import qualified Data.Set as Set
import qualified Data.Array.Unboxed as Array
parse :: String -> UArray (Int, Int) Char
parse s = Array.listArray ((1, 1), (n, m)) . filter (/= '\n') $ s :: UArray (Int, Int) Char
where
n = takeWhile (/= '\n') >>> length $ s
m = List.filter (== '\n') >>> length >>> pred $ s
groupSnd:: Eq b => (a, b) -> (a', b) -> Bool
groupSnd = curry (uncurry (==) <<< snd *** snd)
cartesianProduct xs ys = [(x, y) | x <- xs, y <- ys]
calculateAntitone ((y1, x1), (y2, x2)) = (y1 + dy, x1 + dx)
where
dy = y1 - y2
dx = x1 - x2
antennaCombinations = Array.assocs
>>> List.filter (snd >>> (/= '.'))
>>> List.sortOn snd
>>> List.groupBy groupSnd
>>> map (map fst)
>>> map (\ xs -> cartesianProduct xs xs)
>>> map (filter (uncurry (/=)))
part1 a = antennaCombinations
>>> List.concatMap (map calculateAntitone)
>>> List.filter (Array.inRange (Array.bounds a))
>>> Set.fromList
>>> Set.size
$ a
calculateAntitones ((y1, x1), (y2, x2)) = iterate (bimap (+dy) (+dx)) (y1, x1)
where
dy = y1 - y2
dx = x1 - x2
part2 a = antennaCombinations
>>> List.map (map calculateAntitones)
>>> List.concatMap (List.concatMap (takeWhile (Array.inRange (Array.bounds a))))
>>> Set.fromList
>>> Set.size
$ a
main = getContents
>>= print
. (part1 &&& part2)
. parse
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 part2
Whaaat? It is possible to declare mutliple signatures on one line? π€―
Does that function (.+.
) add tuples/coordinates?
Yup, thatβs right! The function monad is a bit of a mind-bender, but (join f) x == f x x
is a useful thing to remember.
This is so cool, itβs going to replace the lambda in my function pipeline for calculating pairs.
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 us
type 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