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<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
Uiua
Adapting the part one solution for part two took me longer than part one did today, but I didnβt want to change much anymore.
I even got scolded by the interpreter to split the evaluating line onto multiple ones because it got too long.
Canβt say itβs pretty but it does itβs job ^^β
Run with example input here
PartOne β (
&rs β &fo "input-8.txt"
β(β½Β¬β".\n".β΄)
βββ @\n.
:Β€β(:Β€-1β³)
β‘(β‘ββ)
β΄/βββ(β‘(-:β-Β°β)β§
β 2)
⧻β½Β¬:β(/+β+)ββ><,0
)
PartTwo β (
&rs β &fo "input-8.txt"
β(β½Β¬β".\n".β΄βΒ€
β½:ββ‘(>1⧻ββ)
)
βββ @\n.
:Β€β(:Β€-1β³)
β‘(β‘ββ)
βΈβ(
β§
β 2βΒ€
β‘(:Β€β-Β°β
β’(βββ-ββΈβ’
| β
(=0/++β><,0β’))
β‘βββ
)
)
β΄/ββ/ββ
⧻β½Β¬:β(/+β+)ββ><,0
)
&p "Day 8:"
&pf "Part 1: "
&p PartOne
&pf "Part 2: "
&p PartTwo
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.
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();
}
}