#AoC 2024 | Day 8 | Solutions & Spoilers
351 messages · Page 1 of 1 (latest)
def parse_raw(raw: str):
nodes = set()
def classify(c: str):
if c == ".":
return None
nodes.add(c)
return c
return Grid.from_string(raw, classify=classify), nodes
def part_one(data=data):
grid: Grid[str]
grid, nodes = data
antinodes = set()
for i in nodes:
for a, b in product(
grid.find_all(SparseGrid.from_string(i, str, str)), repeat=2
):
if a != b:
dx = a[0] - b[0]
dy = a[1] - b[1]
ax = a[0] + dx
ay = a[1] + dy
bx = b[0] - dx
by = b[1] - dy
if 0 <= ax < grid.width and 0 <= ay < grid.height:
antinodes.add((ax, ay))
if 0 <= bx < grid.width and 0 <= by < grid.height:
antinodes.add((bx, by))
return len(antinodes)
def part_two(data=data):
grid: Grid[str]
grid, nodes = data
antinodes = set()
for i in nodes:
for a, b in product(
grid.find_all(SparseGrid.from_string(i, str, str)), repeat=2
):
if a != b:
dx = a[0] - b[0]
dy = a[1] - b[1]
ax = a[0] + dx
ay = a[1] + dy
bx = b[0] - dx
by = b[1] - dy
antinodes.add(a)
antinodes.add(b)
while 0 <= ax < grid.width and 0 <= ay < grid.height:
antinodes.add((ax, ay))
ax += dx
ay += dy
while 0 <= bx < grid.width and 0 <= by < grid.height:
antinodes.add((bx, by))
bx -= dx
by -= dy
return len(antinodes)
442/246
Lots of not reading the puzzle properly today, unfortunately
with open("day8.txt") as in_f:
DF = in_f.read().strip()
Dl = DF.splitlines()
Db = lmap(str.splitlines, DF.split("\n\n"))
Di = ints(DF)
Df = ints(DF)
ans = 0
nodes = C.defaultdict(str)
for y, line in enumerate(Dl):
for x, c in enumerate(line):
if c == ".":
continue
nodes[complex(x, y)] = c
print(nodes)
check = lambda x: 0 <= x.real < len(Dl[0]) and 0 <= x.imag < len(Dl)
nn = set()
for val in nodes.values():
loc = [k for k in nodes if nodes[k] == val]
print(val, loc)
for l1, l2 in itt.combinations(loc, 2):
# P1
l0 = l1 + (l1 - l2)
l3 = l2 - (l1 - l2)
if check(l0):
nn.add(l0)
if check(l3):
nn.add(l3)
# P2
l0 = l1 + (l1 - l2)
while check(l0):
nn.add(l0)
l0 += l1 - l2
l0 = l2 - (l1 - l2)
while check(l0):
nn.add(l0)
l0 -= l1 - l2
# P2
nn |= set(loc)
ans = len(nn)
print(ans)
``` 260/180, another reminder to finish implementing a proper grid data structure
Oh yeah I forgot about combinations lol
I'm glad I didn't have to find the common divisor.
argg it was unique spots```py
from collections import defaultdict
nodes = defaultdict(set)
for y, line in enumerate(lines):
for x, c in enumerate(line):
if c == '.':
continue
nodes[c].add((x, y))
from itertools import combinations
p1 = set()
p2 = set()
for node, nodess in nodes.items():
for (ax, ay), (bx, by) in combinations(nodess, r=2):
p2.add((ax, ay))
p2.add((bx, by))
dx, dy = bx-ax, by-ay
nx, ny = ax - dx, ay - dy
if is_valid(nx, ny):
p1.add((nx, ny))
while is_valid(nx, ny):
p2.add((nx, ny))
nx -= dx
ny -= dy
mx, my = bx + dx, by + dy
if is_valid(mx, my):
p1.add((mx, my))
while is_valid(mx, my):
p2.add((mx, my))
mx += dx
my += dy
print(len(p1))
print(len(p2))
had an off-by-1 error i guess
what is lmap?
I started by putting a beacon at dx/3, dy/3 and 2dx/3, 2dy/3 which was annoying because that's not what the puzzle asked for but I misread the example
list(map(...))
i had p1 += is_valid(nx, ny) and was wondering what was off
GRID = np.array([list(line) for line in aoc_lube.fetch(year=2024, day=8).splitlines()])
H, W = GRID.shape
FREQUENCIES = [freq for freq in np.unique(GRID) if freq != "."]
def inbounds(y, x):
return 0 <= y < H and 0 <= x < W
def mark_podes(o, slope, podes, part):
if part == 1:
y, x = o + slope
if inbounds(y, x):
podes[y, x] = 1
return
i = 0
y, x = o + i * slope
while inbounds(y, x):
podes[y, x] = 1
i += 1
y, x = o + i * slope
def sum_antipodes(part):
antipodes = np.zeros((H, W), int)
for freq in FREQUENCIES:
for a, b in combinations(np.argwhere(GRID == freq), r=2):
slope = b - a
mark_podes(a, -slope, antipodes, part)
mark_podes(b, slope, antipodes, part)
return antipodes.sum()
def part_one():
return sum_antipodes(1)
def part_two():
return sum_antipodes(2)
this was super messy
and ints?
roughly [int(i) for i in re.findall(r"\d+", x)]
initially had i = 1 above and didn't see the error
honestly the wording of the problem wasn't super clear to me
today was really easy
yeah, felt the same abt p2
quick question yall for p2 what would be the answer to an input like
A..
...
..A
Tbh I found it fine other than the use of 'a' in the P1 example, which I consequently misinterpreted as being the antinodes and # being the nodes
Input wouldn't come like that
2 i believe
But yeah 2
Twice the distance from one as from the other
Oh yeah lol
could it there be an antinode in the middle?
I don't think the input would come like that anyway
today was way too easy for day 8
ah kk
There definitely aren't d = 0 mod 3 pairs (I found this out because I misinterpreted the test)
another brute force problem :/
fr i feel like in previous years we've had at least 1 non bruteable question by now
well i solved the problem its just i think my code doesnt work like 100% of the time 💀
it's simple to do it without brute force
i had a hard time visualizing the logic
what ajskdfhlaksdhjlfhlkajsd
https://old.reddit.com/r/ProgrammingLanguages/comments/eyeonv/whatever_happened_to_the_language_ada/fggqf3q/
part 2 could have been interesting with a much larger map i think
from math import dist
from itertools import pairwise as P
t=open(0).read()
g=[*map(list, t.splitlines())]
k={}
for x in {*t}-{*'\n.#'}:
k[x]=[]
for y, r in enumerate(t.splitlines()):
l=-1
while(l:=r.find(x,l+1))>=0:
k[x].append((y,l))
RL=len(g[0])
CL=len(g)
s=set()
for z in k.values():
for(b,a)in z:
for (d,c)in z:
if(b,a)==(d,c):continue
Yd=d-b
Xd=c-a
v,u,y,x=b-Yd,a-Xd,d+Yd,c+Xd
if 0<=v<CL and 0<=u<RL:s.add((v,u))
if 0<=y<CL and 0<=x<RL:s.add((y,x))
S=set()
for z in k.values():
for(b,a)in z:
for (d,c)in z:
if(b,a)==(d,c):continue
Yd=d-b
Xd=c-a
y,x=b,a
while 0<=y<RL and 0<=x<CL:s.add((y,x));print(y,x);y-=Yd;x-=Xd
y,x=d,c
while 0<=y<RL and 0<=x<CL:s.add((y,x));y+=Yd;x+=Xd
def display_map(
ocoords = None,
postproc = None,
region: tuple[int, int, int, int] | None = None,
) -> None:
display_map: list[list[str]] = list(map(list, g))
if ocoords:
for y, x in ocoords:
if display_map[y][x]=='.':display_map[y][x] = '#'
if postproc:
postproc(display_map)
if region is None:
maxcol = max(map(len, display_map))
for line in display_map:
print(f"{"".join(line):<{maxcol}}")
print("(sized", len(display_map), "x", maxcol, "cells)")
else:
rowstart, colstart, rowend, colend = region
for line in display_map[rowstart : rowend + 1]:
print("".join(line[colstart : colend + 1]))
print("(topleft", (rowstart, colstart),
"sized", rowend - rowstart + 1, "x", colend - colstart + 1,
"cells)")
display_map(s)
print(s)
print(len(s))
I mean, I literally just iterated over antenna locations pairs that shared a common frequency
that whole function is for debugging purposes don't mind it
what's p2? not at home so can't do it yet
echo
echo
-# echo
this is brute force in my book, its pretty damn fast too
because the grid is tiny
all inline map-bound equidistant points
all equidistant positions on the line are antinodes, not just two points
ah
oh wait theres gonna be some cool visualizations for today :)))
I don't think that counts as brute force
you just need to iterate over every pair, no way around it
but actually finding the antinodes is very fast because you just move in the direction until you fall off the map and mark everything
there's probably a smarter way to do this..
Part 1 done :)
complex diff
def solve2(nr, nc, ants):
uniq = set()
for i, (k, v) in enumerate(ants.items()):
for a1, a2 in itertools.combinations(v, 2):
d = a2 - a1
for k in range(-200, 200):
p = a1 - k*d
if 0 <= p.imag < nr and 0 <= p.real < nc:
uniq.add(p)
return len(uniq)
you don't need to check every point for antinode-ness
combinations..?
I GOT OFF BY ONE ERRORS 5 ||FUCKING|| TIMES TODAY. WASTED 15 MINUTES IN PENALTY
why didn't i think of that
Not sure what the word equidistant is doing here but yeah
equispaced, i guess
they are equal in distance from each other though
so it's fair to say "equidistant"
idk, just feels like an uninspired problem
https://github.com/bharadwaj-raju/aoc-2024/tree/main/day8
both p1 and p2 take exactly the same no of lines
at least with day6 there was richness with the RTL vs LTR distinction
definitely a boring problem
ah yes off by one errors... a programmers favourite
even the lore for this day feels disconnected from finding the Chief, lol
OBOEちゃん doesn't mess around
i lowkey havent been reading the stories at all
wait does this include stuff in between
like if you had ..a...a.. are all the dots antinodes
same
Today was surprisingly easy.
use crate::utility::{as_grid, find_limits, get_file, Grid, Position};
use itertools::Itertools;
use std::collections::HashSet;
type Dimensions = (i64, i64, i64, i64);
type Positions = HashSet<Position>;
fn parse(s: &str) -> (Grid, Dimensions) {
let mut base = as_grid(s);
let dimensions = find_limits(base.keys().cloned());
base.retain(|_k, v| *v != '.');
(base, dimensions)
}
fn within(d: Dimensions, poss: Position) -> bool {
let (top, left, bottom, right) = d;
!(poss.im < top || poss.im > bottom || poss.re < left || poss.re > right)
}
fn antinodes(d: Dimensions, px: Positions) -> Positions {
px.into_iter()
.permutations(2)
.map(|r| {
let a = r[0];
let b = r[1];
let delta = b - a;
b + delta
})
.filter(|x| within(d, *x))
.collect()
}
fn antinodes2(d: Dimensions, px: Positions) -> Positions {
px.into_iter()
.permutations(2)
.flat_map(|r| {
let a = r[0];
let b = r[1];
let delta = b - a;
let mut pos = a;
std::iter::from_fn(move || {
pos += delta;
if within(d, pos) {
Some(pos)
} else {
None
}
})
})
.collect()
}
pub fn part1() -> usize {
let (base, dimensions) = parse(&get_file(8));
let frequencies: HashSet<char> = base.values().cloned().collect();
let alls: Positions = frequencies
.into_iter()
.flat_map(|c| {
let poses = base
.iter()
.filter(|(_k, v)| **v == c)
.map(|t| *t.0)
.collect();
antinodes(dimensions, poses)
})
.collect();
alls.len()
}
Part2 is identical to part1 but with antinodes2 instead of antinodes.
if you had ..a...a.. then only the 2 as are antinodes since the rest are out-of-bounds
nah the antinodes are the same distance from each other as the antennas
this still feels messy:
def sum_antipodes(part):
antipodes = np.zeros((H, W), int)
for freq in FREQUENCIES:
for a, b in combinations(np.argwhere(GRID == freq), r=2):
c = b - a
i = 1 if part == 1 else 0
while True:
done = True
y, x = a - i * c
if inbounds(y, x):
antipodes[y, x] = 1
done = False
y, x = b + i * c
if inbounds(y, x):
antipodes[y, x] = 1
done = False
if done or part == 1:
break
i += 1
return antipodes.sum()
whats the way to do this with a single condition in the while loop
And part two done :)
two while loops :D
i feel like theres a joke im not getting here
i substituted the while loop for an iter.count and called it a day
or i should probably learn numpy idk
oh well
Wow today was pretty fast computationally even with way too much hashing ```
8 1 69µs .01% .06%
8 2 131µs .01% .12%
wait a minute i already made one
??? why didn't i use this what
the government doesnt want you to know this but theres a builtin vector class
its called complex
meh..
how dare u
doesn't have a full precision variant
hm actually, I think today's problem might actually be wrong
how did you get to that conclusion
if you have two antenna separated on the grid by like a (2, 2) displacement, then technically you should be marking every (1,1) offset, not (2,2)
but the problem expects (2,2)
do you mean like in terms of real physics or something
And today's code https://paste.rs/XzcVJ.rs
because we had elephants holding operators yesterday
Yeah, Python
no I mean like, "in line" would be (1,1)
the wording is vague, but if we restrict ourselves to the integer grid this is what that would mean
I'm glad I didn't have any prompt misunderstanding issues. The biggest issue I had was forgetting to sanitize the tests.
that's why we have examples :p
marking as per p1 or p2?
doesnt this cover it
Yeah, Python's complex numbers are implemented as floats, so you have to do messy int conversion every time you do i.real or whatever. It's still my favourite language, but it's not perfect.
(ugh, I hate how enter is next to apostrophe and I mistype and it posts early)
This observation only matters when the lcm between the two offset coords is not 1
possibly by coincidence this is the case for all the examples provided
It matters when the gradient of the points is (1, 1)
julia is kinda nice that way because Complex is generic
so you can have Complex{Int32} and Complex{Float32}
yeah exactly
which include like, an antenna offset of (2,2) and (3,3) etc
not very highly optimized here, used a dict and then just extended based on slope
data = open(filename).read().splitlines()
map = {(x,y):c for x,row in enumerate(data) for y,c in enumerate(row)}
def extend(a,b,slope):
while (a,b) in map:
anod.add((a,b))
a,b = (a+slope[0], b+slope[1])
freq = set(map.values())-{'.'}
antn = {a:{k for k,v in map.items() if a==v} for a in freq}
anod = set()
for fr,locs in antn.items():
for (a,b),(c,d) in combinations(locs,2):
extend(a,b,((a-c),(b-d)))
extend(a,b,((c-a),(d-b)))
print('part 2', len(anod))
same
ah i got your point now
it would also matter when the gradient is like (k, 0) or (0, k)
just looked, and my test case actually has examples where the LCM is not 1, and I got stars despite not handling that situation, so the problem doesn't consider it either
I did enjoy using combinations(locs,2) here. And I always enjoy using sets.
def solve(data):
data = data.split("\n")
loc = defaultdict(list)
for i, row in enumerate(data):
for j, char in enumerate(row):
if char != ".":
loc[char].append((i, j))
c = 0
s = set()
n = len(data)
m = len(data[0])
def lies(x, y):
return 0 <= x < n and 0 <= y < m
for key, lis in loc.items():
for p in product(lis, repeat=2):
if p[0] == p[1]:
continue
p1, p2 = sorted(p)
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
point = p1
while lies(*point):
if point not in s:
s.add(point)
c += 1
point = point[0] - dx, point[1] - dy
point = p2
while lies(*point):
if point not in s:
s.add(point)
c += 1
point = point[0] + dx, point[1] + dy
i think that's possible. but gladly it wasn't.
the slope thing is repetitive could have avoided that
ah, I actually have a variable slope in my code.
def extend(a,b,slope):
while (a,b) in map:
anod.add((a,b))
a,b = (a+slope[0], b+slope[1])
LCM* (least common multiple)
yeah ic
yea cause my friends were pointing it out in my code so i like maybe i just somehow got lucky w/ input? but ig not
def mark_podes(o, slope, podes, part):
if part == 1:
y, x = o + slope
if inbounds(y, x):
podes[y, x] = 1
return
i = 0
y, x = o + i * slope
while inbounds(y, x):
podes[y, x] = 1
i += 1
y, x = o + i * slope
def sum_antipodes(part):
antipodes = np.zeros((H, W), int)
for freq in FREQUENCIES:
for a, b in combinations(np.argwhere(GRID == freq), r=2):
slope = b - a
mark_podes(a, -slope, antipodes, part)
mark_podes(b, slope, antipodes, part)
return antipodes.sum()
dunno if this is looking better
Looks like I did more or less like others here
from collections import defaultdict
from itertools import combinations
from pathlib import Path
inp = Path("in.txt").read_text().strip().splitlines()
height = len(inp)
width = len(inp[0])
antennas = defaultdict(list)
for i, row in enumerate(inp):
for j, signal in enumerate(row):
if signal != ".":
antennas[signal].append((i, j))
antinodes = set()
for signal, positions in antennas.items():
for (y1, x1), (y2, x2) in combinations(positions, 2):
dy, dx = y2 - y1, x2 - x1
if 0 <= y2 + dy < height and 0 <= x2 + dx < width:
antinodes.add((y2+dy, x2+dx))
if 0 <= y1 - dy < height and 0 <= x1 - dx < width:
antinodes.add((y1-dy, x1-dx))
print(len(antinodes))
antinodes2 = set()
for signal, positions in antennas.items():
for (y1, x1), (y2, x2) in combinations(positions, 2):
dy, dx = y2 - y1, x2 - x1
ay, ax = y1 + dy, x1 + dx
while 0 <= ay < height and 0 <= ax < width:
antinodes2.add((ay, ax))
ay, ax = ay + dy, ax + dx
ay, ax = y2 - dy, x2 - dx
while 0 <= ay < height and 0 <= ax < width:
antinodes2.add((ay, ax))
ay, ax = ay - dy, ax - dx
print(len(antinodes2))
maybe wording could be better for today (i read every 5th word)
whats path?
nvm
thats just path
i did not even read every 5th word
path is just path
i think i read exactly 20 words
could repeatedly add slope to y, x instead of having a separate i
I'm very glad I already improved my day 5 to use combinations so I already had code to reuse, average tiny rust std moment.
Please read the message you just got from the bot 🙂
dang can’t say that
mb just say it
well, i break up the array into a tuple each time, i can't directly add to the tuple
saw*
You could write a function that takes it as an param?
this one line doesn't really need a separate function y, x = o + i * slope
i don't think
I was annoyed at first that the problem didn't define what distance to use, until I realized it doesn't matter
i could just break slope up into dy, dx and repeatedly add i guess
yeah it's sad how complex numbers don't work nicely with rectangular grids
def mark_podes(y, x, dy, dx, podes, part):
if part == 1:
if inbounds(y + dy, x + dx):
podes[y + dy, x + dx] = 1
return
while inbounds(y, x):
podes[y, x] = 1
y += dy
x += dx
def sum_antipodes(part):
antipodes = np.zeros((H, W), int)
for freq in FREQUENCIES:
for a, b in combinations(np.argwhere(GRID == freq), r=2):
mark_podes(*a, *a - b, antipodes, part)
mark_podes(*b, *b - a, antipodes, part)
return antipodes.sum()
def part2(lines):
h, w = len(lines), len(lines[0])
antennas: dict[str, list[tuple[int, int]]] = get_antennas(lines)
antinodes = set()
for k in antennas:
for p1, p2 in it.combinations(antennas[k], 2):
p1, p2 = sorted((p1, p2), key=lambda x: x[0])
m = (p2[0] - p1[0], p2[1] - p1[1])
for s in (1, -1):
for i in it.count():
antinode = (
p2[0] + i * m[0] * s,
p2[1] + i * m[1] * s,
)
if 0 <= antinode[0] < h and 0 <= antinode[1] < w:
antinodes.add(antinode)
else:
break
print(len(antinodes))
there, got rid of the i
removed unnecessary variables:
data = open(filename).read().splitlines()
map = {(x,y):c for x,row in enumerate(data) for y,c in enumerate(row)}
def extend(a,b,slope):
while (a,b) in map:
anod.add((a,b))
a,b = (a+slope[0], b+slope[1])
anod = set()
for fr in set(map.values())-{'.'}:
locs = {k for k,v in map.items() if fr==v}
for (a,b),(c,d) in combinations(locs,2):
extend(a, b, ((a-c),(b-d)))
extend(a, b, ((c-a),(d-b)))
print(len(anod))
this isn't valid python 😦
while podes[y, x] := inbounds(y, x):
wdym? i dont see it
parentheses?
its right here #1315183142348263497 message
can only walrus with identifier
oh
I really enjoyed today's puzzle
Though I frustrated myself with tons of small typos
Ah wel
my bed time was several hours ago, but i kept myself awake with a pot of coffee
is that a good thing?
I'm not as young as I used to be and don't do that any more.
well not via coffee anyway.
why would it be a good thing
maybe try```py
for podes[y, x] in takewhile(bool, map(lambda _: inbounds(y, x), iter(int,1))):
:D
but it is a thing i did
aoc single handily ruining my entire sleep schedule :(
but yk gotta do it for leaderboard
for once my timezone is rather convenient
i don't really ever make the leaderboard
but i can usually get top 1000, which is fair enough, this is the only competitive programming i do
inbounds() already returns a boolean, so ```py
for podes[y, x] in iter(lambda: inbounds(y, x), False):
oooo nice
I'm just happy competing with you guys 😁
competition is good for the market
yea but i’ve got some private leaderboard positions that i gotta fight for
need some competition for visualizations
ur visualizations are really nice always look forward to looking at them
aight sleepy time tho, atp aoc is acc gonna invert my sleep schedule
goodnight 💋
good enough: https://paste.pythondiscord.com/SBZQ
def parser(raw_data: TextIO):
antennas = defaultdict(set)
for x, row in enumerate(raw_data.read().splitlines()):
for y, v in enumerate(row):
if v != '.':
antennas[v].add(x+1j*y)
return antennas, x + 1, y + 1
def full_solver(antennas: dict[str, set[complex]], height: int, width: int):
def within(z: complex) -> bool:
return 0 <= z.real < height and 0 <= z.imag < width
p1_antinodes = set()
p2_antinodes = set()
for antenna, locs in antennas.items():
for z1, z2 in combinations(locs, 2):
d = z2 - z1
if within(z2 + d):
p1_antinodes.add(z2 + d)
if within(z1 - d):
p1_antinodes.add(z1 - d)
while within(z2):
p2_antinodes.add(z2)
z2 += d
while within(z1):
p2_antinodes.add(z1)
z1 -= d
return len(p1_antinodes), len(p2_antinodes)
# part 1
from collections import defaultdict # noqa: D100, INP001
from itertools import combinations
from math import dist
with open("input.txt") as f: # noqa: PTH123
grid = [list(line.strip()) for line in f]
antennae = defaultdict(list)
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] != ".":
antennae[grid[i][j]].append((i, j))
result = 0
for members in antennae.values():
for member1, member2 in combinations(members, r=2):
distance = dist(member1, member2)
distance2 = 2 * distance
for i in range(len(grid)):
for j in range(len(grid)):
if grid[i][j] != "#":
dist1 = dist(member1, (i, j))
dist2 = dist(member2, (i, j))
d = [dist1, dist2]
if distance in d and distance2 in d:
grid[i][j] = "#"
result += 1
print(result) # noqa: T201
Well, I certainly know how to complicate simple things. 😭
# part 2
from collections import defaultdict # noqa: D100, INP001
from itertools import combinations
from math import dist
with open("input.txt") as f: # noqa: PTH123
grid = [list(line.strip()) for line in f]
antennae = defaultdict(list)
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] != ".":
antennae[grid[i][j]].append((i, j))
result = 0
for members in antennae.values():
for member1, member2 in combinations(members, r=2):
for i in range(len(grid)):
for j in range(len(grid)):
if grid[i][j] != "#":
x1, y1 = member1
x2, y2 = member2
x3, y3 = (i, j)
# some collinear formula
if abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) == 0:
grid[i][j] = "#"
result += 1
print(result) # noqa: T201
you can make it faster and simpler by calculating the x offset and y offset between the member{1,2} pair, and then add that to the members once each (for p1) and until you fall off the grid (for p2)
Right. Thanks!
def part_1():
unique_chars = set(sample)
unique_chars.remove("\n")
unique_chars.remove(".")
grid = [list(row) for row in sample.splitlines()]
grid_size = (len(grid[0]), len(grid))
antenna_map: defaultdict[str, list[Antenna]] = defaultdict(list)
for y, row in enumerate(grid):
for x, char in enumerate(row):
if char in unique_chars:
antenna_map[char].append(Antenna(char=char, cell_pos=Vec2(x, y)))
antinodes: set[Vec2] = set()
for char, antennas in antenna_map.items():
for a1, a2 in itertools.combinations(antennas, 2):
x_diff = a1.cell_pos.x - a2.cell_pos.x
y_diff = a1.cell_pos.y - a2.cell_pos.y
antinode_1_pos = Vec2(
a2.cell_pos.x + (x_diff * 2), a2.cell_pos.y + (y_diff * 2)
)
antinode_2_pos = Vec2(
a1.cell_pos.x - (x_diff * 2), a1.cell_pos.y - (y_diff * 2)
)
if is_vec_in_bounds(antinode_1_pos, grid_size):
antinodes.add(antinode_1_pos)
if is_vec_in_bounds(antinode_2_pos, grid_size):
antinodes.add(antinode_2_pos)
print_grid(grid, antinodes)
utils.answer(1, len(antinodes))
Woohoo!
that looks like pycharm
it is pycharm
My part two is so hideous
grid_dict = {}
for line_no, line in enumerate(data.split('\n')):
for x,y in enumerate(line):
if y != '.' and y not in grid_dict.keys():
grid_dict[f'{y}'] = [(line_no, x)]
elif y != '.' and y in grid_dict.keys():
grid_dict[f'{y}'].append((line_no, x))
check_boundary = lambda check: all((len(data.split('\n'))-1 >= check[0] >= 0, len(data.split('\n'))-1 >= check[1] >= 0))
def part_one():
pre_antinodes = []
for pair_list in grid_dict.values():
path_difference = list(map(lambda pair: [(pair[0]-x[0], pair[-1]-x[-1]) for x in pair_list if pair != x], pair_list))
antinode_placements = [
[ (pl[0]+pd[0], pl[1]+pd[1])
for pd in path_difference[pl_no]
if check_boundary((pl[0]+pd[0], pl[1]+pd[1]))]
for pl_no, pl in enumerate(pair_list)
]
for an in antinode_placements:
pre_antinodes += an
return len(set(tuple(pre_antinodes)))
def part_two():
pre_antinodes = []
for pair_list in grid_dict.values():
path_difference = list(map(lambda pair: [(pair[0]-x[0], pair[-1]-x[-1]) for x in pair_list if pair != x], pair_list))
for pl_no, pl in enumerate(pair_list):
pre_antinodes += [pl]
for pd in path_difference[pl_no]:
an_beginning = (pl[0]+pd[0], pl[1]+pd[1])
while check_boundary(an_beginning):
pre_antinodes.append(an_beginning)
an_beginning = (an_beginning[0]+pd[0], an_beginning[1]+pd[1])
return len(set(tuple(pre_antinodes)))
was there a better way to do part two?
5 loops. 💀 🫢
https://paste.pythondiscord.com/BC6Q
$ hyperfine -w 100 -r 1000 -N ./solution
Benchmark 1: ./solution
Time (mean ± σ): 544.5 µs ± 89.2 µs [User: 407.6 µs, System: 44.2 µs]
Range (min … max): 419.8 µs … 891.1 µs 1000 runs
the real hideous thing is that you split the data twice each time in check_boundary
f'{y}' is also pretty hideous, thats just y (you could also use a defaultdict with set as the values instead of the thing you have now)
and tbh list(map(lambda x: e, xs)) is so ugly and such a code smell to me. [e for x in xs] is so much better in all ways
xs += [x] is also less efficient than xs.append(x)
also.. set(tuple(xs))..
why not just set(xs) or like make it a set right away instead of appending and then set()-ing
Nice easy day today I feel like
use itertools::Itertools;
use std::{
collections::{HashMap, HashSet},
ops::{Add, Sub},
};
advent_of_code::solution!(8);
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
impl Sub for Point {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
}
}
}
fn solve(input: &str, resonant_harmonics: bool) -> u32 {
let grid: Vec<Vec<char>> = input.lines().map(|line| line.chars().collect()).collect();
let height = grid.len().try_into().expect("Height fits into i32");
let width = grid[0].len().try_into().expect("Width fits into i32");
let boundary_check =
|point: Point| (point.x >= 0 && point.x < width) && (point.y >= 0 && point.y < height);
let mut antennas = HashMap::new();
for (y, row) in grid.iter().enumerate() {
for (x, cell) in row.iter().enumerate() {
if *cell == '.' {
continue;
}
let point = Point {
x: x.try_into().expect("x fits into i32"),
y: y.try_into().expect("y fits into i32"),
};
let antenna_points = antennas.entry(*cell).or_insert(vec![]);
antenna_points.push(point);
}
}
let mut antinodes = HashSet::new();
for points in antennas.into_values() {
for (point_1, point_2) in points
.into_iter()
.combinations(2)
.map(|pair| (pair[0], pair[1]))
{
if resonant_harmonics {
antinodes.insert(point_1);
antinodes.insert(point_2);
}
let delta = point_1 - point_2;
let mut antinode_1 = point_1;
loop {
antinode_1 = antinode_1 + delta;
if !boundary_check(antinode_1) {
break;
}
antinodes.insert(antinode_1);
if !resonant_harmonics {
break;
};
}
let mut antinode_2 = point_2;
loop {
antinode_2 = antinode_2 - delta;
if !boundary_check(antinode_2) {
break;
}
antinodes.insert(antinode_2);
if !resonant_harmonics {
break;
};
}
}
}
antinodes
.len()
.try_into()
.expect("Antinodes count fits into u32")
}
pub fn part_one(input: &str) -> Option<u32> {
Some(solve(input, false))
}
pub fn part_two(input: &str) -> Option<u32> {
Some(solve(input, true))
}
i almost identified as kilometers per second trying to do it in c
but hey half millisecond runtime
coord antennas[26 * 2 + 10][16]; was very smart i think
I love how in Rust, if you have your "business" logic correct everything else falls into place
I just run my tests and so long as they compile, they work pretty much perfectly every time
also, did anyone have only one instance of a particular antenna type? Thought it was curious that Part 2 called that case out but I don't see it in my input
no such thing in my input either
also I love itertools combinations
eh
its just
for (i = 0; i < n; i++) {
a = xs[i]
for (j = i + 1; j < n; j++) {
b = xs[j]
}
}
it's a minor convenience, sure, but it's just one thing I don't have to worry about
one less thing to screw up
also this was me today (https://www.reddit.com/r/adventofcode/comments/1h9bmsm/2024_aoc_day_8_what_does_this_even_mean/ )
the actual finding antinodes positions part could be simd'ed pretty good i think:
store the positions as SoA instead of AoS: load, sub, mul by 2 (shift?), add, cmp
but i wont thats too much pain
My solution to day8, i hope it helps https://github.com/st1vms/Advent-of-Code/tree/main/AoC_2024/day_8
from collections import defaultdict
from itertools import combinations
with open("input.txt") as f:
data = f.read()
lines = data.splitlines()
rows = len(lines)
cols = len(lines[0])
frequencies = {*data} - {".", "\n"}
frequencies_locations = defaultdict(list)
for i, line in enumerate(lines):
for j, char in enumerate(line):
if char in frequencies:
frequencies_locations[char].append((i, j))
antinodes = set()
for freq, locations in frequencies_locations.items():
for (x1, y1), (x2, y2) in combinations(locations, 2):
dr, dc = -(x1 - x2), -(y1 - y2)
antinodes.add((x1, y1))
antinodes.add((x2, y2))
i = 0
while True:
antinode_x = (x1 - dr * i, y1 - dc * i)
if antinode_x[0] < 0 or antinode_x[0] >= rows:
break
if antinode_x[1] < 0 or antinode_x[1] >= cols:
break
antinodes.add(antinode_x)
i += 1
j = 0
while True:
antinode_y = (x2 + dr * j, y2 + dc * j)
if antinode_y[0] < 0 or antinode_y[0] >= rows:
break
if antinode_y[1] < 0 or antinode_y[1] >= cols:
break
antinodes.add(antinode_y)
j += 1
print(len(antinodes))
can this be optimized? its for p2
I love the readmes
True. For check boundary i didn't want to define data.split('\n') above thats why. that f'{y}' is actually criminal though.I actually don't remember why i used xs += [x]ah and for the last i have no clue either 😭
Todays wording was really wack tho. Had to be like wtf was it asking for p2.
ima make a different implementation for p2 i have an idea
huh, I assumed that for the diagonal ones I needed to reduce the distance .. like there could be two antenna (1,1, and 5,9) so the distances is (4,8) which means I need to use a (1,2) distance to iterate on to find the points so I can also get (2,3) which is in line with them.
But my code gets the right answer even if I don't do that....
import itertools
from collections import defaultdict
from collections.abc import Callable
from pathlib import Path
Vector2 = tuple[int, int]
DATA_PATH = Path(__file__).parent / "input"
def load_data() -> list[list[str]]:
with open(DATA_PATH) as file:
data = list(map(list, file.read().splitlines())) # noqa
return data
def vec2d_neg(vec: Vector2, /) -> Vector2:
x, y = vec
return -x, -y
def vec2d_add(a: Vector2, b: Vector2, /) -> Vector2:
x1, y1 = a
x2, y2 = b
return x1 + x2, y1 + y2
def vec2d_sub(a: Vector2, b: Vector2, /) -> Vector2:
b = vec2d_neg(b)
return vec2d_add(a, b)
def vec2d_within_bounds_factory(
x: int, y: int, w: int, h: int
) -> Callable[[Vector2], bool]:
def bound_checker(vec: Vector2, /) -> bool:
v_x, v_y = vec
return x <= v_x < x + w and y <= v_y < y + h
return bound_checker
def get_frequency_to_antenna_location(
data: list[list[str]],
) -> dict[str, list[Vector2]]:
frequency_to_antenna_location: dict[str, list[Vector2]] = defaultdict(list)
for y, row in enumerate(data):
for x, cell in enumerate(row):
if cell == ".":
continue
frequency_to_antenna_location[cell].append((x, y))
return frequency_to_antenna_location
def part_one():
data = load_data()
frequency_to_antenna_location = get_frequency_to_antenna_location(data)
vec2d_in_world = vec2d_within_bounds_factory(0, 0, len(data[0]), len(data))
antinode_locations: set[Vector2] = set()
for antennas in frequency_to_antenna_location.values():
for a, b in itertools.combinations(antennas, r=2):
diff = vec2d_sub(a, b)
pos_1 = vec2d_add(a, diff)
if vec2d_in_world(pos_1):
antinode_locations.add(pos_1)
pos_2 = vec2d_sub(b, diff)
if vec2d_in_world(pos_2):
antinode_locations.add(pos_2)
print(len(antinode_locations))
def part_two():
data = load_data()
frequency_to_antenna_location = get_frequency_to_antenna_location(data)
vec2d_in_world = vec2d_within_bounds_factory(0, 0, len(data[0]), len(data))
antinode_locations: set[Vector2] = set()
for antennas in frequency_to_antenna_location.values():
for a, b in itertools.combinations(antennas, r=2):
antinode_locations.update([a, b])
diff = vec2d_sub(a, b)
while True:
pos_1 = vec2d_add(a, diff)
if vec2d_in_world(pos_1):
antinode_locations.add(pos_1)
a = pos_1
else:
break
while True:
pos_2 = vec2d_sub(b, diff)
if vec2d_in_world(pos_2):
antinode_locations.add(pos_2)
b = pos_2
else:
break
print(len(antinode_locations))
if __name__ == "__main__":
part_one()
part_two()
oh hey you're using a defaultdict too
and itertools.combinations
those are some thicc names though :kekw:
Here's mine. sets and over-iterating. part 1 and 2
i like this
You've earned the title: NeverNester!
should be combinations I switched to permutations when I was getting Part1 wrong out of desperation but I had a bug elsewhere. 😂
haha
you can use complex numbers to represent coords
https://github.com/cefqrn/advent-of-code/blob/main/2024/08/a.adb
looking at the pascal snake case thing for today i just realized i used snake case in pascal for day 7 whoops
today was done in rust bc my gut instinct would be that this would need it, but honestly, i think the solution would've been simpler in haskell https://paste.pythondiscord.com/SSIQ
maybe i'll revisit and rewrite it later given the time
...the time is now >:) https://paste.pythondiscord.com/XA4Q
i just noticed i made a tiny mistake when copying over a line in the part 2 resonance function, buut it ends up being cancelled out / not mattering :P
both parts
This is my solution today: ```py
import sys, collections, itertools
with open(sys.argv[1]) as file:
content = file.read()
cells = set()
antennas_by_frequency = collections.defaultdict(set)
for r, line in enumerate(content.splitlines()):
for c, char in enumerate(line):
cell = r + c*1j
cells.add(cell)
if char != '.':
antennas_by_frequency[char].add(cell)
antinodes = set()
for frequency, antennas in antennas_by_frequency.items():
for a, b in itertools.combinations(antennas, 2):
antinodes |= {2a - b, 2b - a} & cells
p1 = len(antinodes)
print(p1)
assert len(content.splitlines()) <= 50
antinodes = set()
for frequency, antennas in antennas_by_frequency.items():
for a, b in itertools.combinations(antennas, 2):
antinodes |= {b + (b-a)*i for i in range(-50, +50)} & cells
p2 = len(antinodes)
print(p2)
Today is an obvious Coordinate map over 2D list day. But even when you could go either way, I'm starting to prefer the map option more
for a straight map of coordinates to cells, you don't have to worry about boundary checking if you have a new coordinate - you just check if it's present in the map
It's ok when your map is relatively small
i mean, what is a 2D array, if not a fixed-size hashmap with 2D coordinate keys? :P
hello lua
i was about to errm acktually you but no you're right, tho only specifically in that "arrays are just hashmaps" direction
Yeah, exactly. Once I start dealing explicitly with indices I get discombobulated and start making off-by-one errors everywhere
It also helps when you have things like irregularly shaped maps.
Today I did boundary checking just because I didn't want to keep a full map of coordinates just to do that, but in general where I can use a fullmap, I will do so
#include <cstdio>
#include <cmath>
#include <vector>
#include <utility>
#define pii pair<int, int>
#define N 50
using namespace std;
char a[55][55];
bool m[55][55];
vector<pii> v[128];
bool ir(int x) {
return 0 <= x && x < N;
}
void mark(pii A, pii B) {
int dx = abs(A.first-B.first);
int dy = abs(A.second-B.second);
if (dx == 0 && dy == 0) return;
int ax1 = min(A.first, B.first)-dx;
int ax2 = max(A.first, B.first)+dx;
int ay1 = min(A.second, B.second)-dy;
int ay2 = max(A.second, B.second)+dy;
if (ir(ax1) && ir(ay1) && a[ax1+dx][ay1+dy] == a[A.first][A.second]) m[ax1][ay1] = true;
if (ir(ax1) && ir(ay2) && a[ax1+dx][ay2-dy] == a[A.first][A.second]) m[ax1][ay2] = true;
if (ir(ax2) && ir(ay1) && a[ax2-dx][ay1+dy] == a[A.first][A.second]) m[ax2][ay1] = true;
if (ir(ax2) && ir(ay2) && a[ax2-dx][ay2-dy] == a[A.first][A.second]) m[ax2][ay2] = true;
}
int main() {
FILE *f = fopen("8.in", "r");
int r=0;
for (int i=0; i<N; i++) fscanf(f, "%s", a[i]);
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
if (a[i][j] != '.') v[a[i][j]].push_back({i,j});
}
}
for (int i='0'; i<='z'; i++) {
for (auto A: v[i]) for (auto B: v[i]) mark(A, B);
}
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
if(m[i][j]) r++;
}
}
printf("%d", r);
}
part one
#include <cstdio>
#include <cmath>
#include <vector>
#include <utility>
#define pii pair<int, int>
#define N 50
using namespace std;
char a[55][55];
bool m[55][55];
vector<pii> v[128];
bool ir(int x) {
return 0 <= x && x < N;
}
void generate(int x, int y, int dx, int dy) {
for (int i=x+dx, j=y+dy;; i+=dx, j+=dy) {
if (!(ir(i) && ir(j))) break;
m[i][j] = true;
}
}
void mark(pii A, pii B) {
int dx = abs(A.first-B.first);
int dy = abs(A.second-B.second);
if (dx == 0 && dy == 0) return;
int ax1 = min(A.first, B.first);
int ax2 = max(A.first, B.first);
int ay1 = min(A.second, B.second);
int ay2 = max(A.second, B.second);
if (a[ax1][ay1] == a[A.first][A.second])
generate(ax1, ay1, -dx, -dy);
if (a[ax1][ay2] == a[A.first][A.second])
generate(ax1, ay2, -dx, dy);
if (a[ax2][ay1] == a[A.first][A.second])
generate(ax2, ay1, dx, -dy);
if (a[ax2][ay2] == a[A.first][A.second])
generate(ax2, ay2, dx, dy);
}
int main() {
FILE *f = fopen("8.in", "r");
int r=0;
for (int i=0; i<N; i++) fscanf(f, "%s", a[i]);
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
if (a[i][j] != '.') v[a[i][j]].push_back({i,j});
}
}
for (int i='0'; i<='z'; i++) {
for (auto A: v[i]) for (auto B: v[i]) mark(A, B);
}
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
if(m[i][j]||a[i][j]!='.') r++;
}
}
printf("%d", r);
}
part two
i'm more used to using cstdio than iostream tho
i mean come to think of it, that solution isn't elegant at all since the antenna itself can be the mark, why would i need to start with x+dx and y+dy lol
wow I was really surprised it was very simple to go from part 1 to part 2
I wanted to try writing less repetitive code for calculating anti-nodes but this is the current status...
def find_nodes(p1: tuple[int, int], p2: tuple[int, int], R: int, C: int, loop: bool) -> List[tuple[int, int]]:
def add_nodes(y: int, x: int, dy: int, dx: int, condition: callable) -> List[tuple[int, int]]:
_nodes = []
while condition(y, x):
y += dy
x += dx
_nodes.append((y, x))
if not loop:
break
return _nodes
p1y, p1x = p1
p2y, p2x = p2
slope = (p1y - p2y) / (p1x - p2x)
x_diff = abs(p1x - p2x)
y_diff = abs(p1y - p2y)
nodes = []
if slope >= 0:
if p1y > p2y:
nodes += add_nodes(p1y, p1x, y_diff, x_diff, lambda y, x: y + y_diff < R and x + x_diff < C)
nodes += add_nodes(p2y, p2x, -y_diff, -x_diff, lambda y, x: y - y_diff >= 0 and x - x_diff >= 0)
else:
nodes += add_nodes(p1y, p1x, -y_diff, -x_diff, lambda y, x: y - y_diff >= 0 and x - x_diff >= 0)
nodes += add_nodes(p2y, p2x, y_diff, x_diff, lambda y, x: y + y_diff < R and x + x_diff < C)
else:
if p1y > p2y:
nodes += add_nodes(p1y, p1x, y_diff, -x_diff, lambda y, x: y + y_diff < R and x - x_diff >= 0)
nodes += add_nodes(p2y, p2x, -y_diff, x_diff, lambda y, x: y - y_diff >= 0 and x + x_diff < C)
else:
nodes += add_nodes(p1y, p1x, -y_diff, x_diff, lambda y, x: y - y_diff >= 0 and x + x_diff < C)
nodes += add_nodes(p2y, p2x, y_diff, -x_diff, lambda y, x: y + y_diff < R and x - x_diff >= 0)
return nodes
Also looks like many people used itertools.combinations. Noted for next time!
unique_nodes = set()
for x in antennas:
cells = antennas[x]
pairs = set()
for i in range(len(cells)):
if loop:
unique_nodes.add(cells[i]) # Part 2: Include antennas as nodes
for j in range(i + 1, len(cells)):
pairs.add((cells[i], cells[j]))
for pair in pairs:
nodes = find_nodes(pair[0], pair[1], R, C, loop)
unique_nodes.update(nodes) # Update the set, adding elements from the List of nodes
hey, can anyone try to help me spot the issue here?
import re
from itertools import combinations
contents = open("day08_test.txt").readlines()
# Part 1
pattern = "[^.]"
antennas = {}
antinodes = []
for i in range(len(contents)):
line = contents[i].strip()
while re.search(pattern, line):
match = re.search(pattern, line)
line = line[match.span()[1]:]
try:
antennas[match.group()].append([i, match.span()[0]])
except KeyError:
antennas[match.group()] = [[i, match.span()[0]]]
for key, coordinates in antennas.items():
for start, end in combinations(coordinates, 2):
distance = [abs(end[0] - start[0]), abs(end[1] - start[1])]
if start[0] < end[0] and start[1] > end[1]:
antinode = [start[0] - distance[0], start[1] + distance[1]]
antinode2 = [end[0] + distance[0], end[1] - distance[1]]
else:
antinode = [end[0] + distance[0], end[1] + distance[1]]
antinode2 = [start[0] - distance[0], start[1] - distance[1]]
print(f"start {start}, end {end}, distance {distance}, antinodes {antinode},{antinode2}")
if -1 < antinode[0] < len(contents) and -1 < antinode[1] < len(contents[0]) and antinode not in antinodes:
antinodes.append(antinode)
if -1 < antinode2[0] < len(contents) and -1 < antinode2[1] < len(contents[0]) and antinode2 not in antinodes:
antinodes.append(antinode2)
print(sorted(antinodes))
print(len(antinodes))
this works with test input from today's example but doesn't work with real input 😦
(it's just part 1)
your else catches the remaining 3 of possible combinations of the coordinates
start[0] < end[0] and start[1] <= end[1]
start[0] >= end[0] and start[1] <= end[1]
start[0] >= end[0] and start[1] > end[1]
right, i realized in the meantime, working on it, thx
very proud of the conciseness here
with open('input.txt') as file:
city_map = np.array([list(line.strip()) for line in file])
antinodes = np.zeros_like(city_map, dtype=bool)
for antenna in np.unique(city_map):
if antenna == '.':
continue
positions = np.argwhere(city_map == antenna)
for a, b in itertools.combinations(positions, 2):
vector = (b - a)
vector //= np.gcd(*vector) # simplify x:y ratio
pos = a.copy()
while pos_in_map(pos - vector, city_map):
pos -= vector
while pos_in_map(pos, city_map):
antinodes[*pos] = True
pos += vector
print(np.sum(antinodes))
I really feel like today could be formularized but i aint smart enough to do that and it runs in .02 secs so 🤷♀️
Yeah, I'm trying to figure out what Andrew did up there
Don't quite get this part
vector = (b - a)
vector //= np.gcd(*vector) # simplify x:y ratio
vector must be the difference in row and column between two points. But how does division of the greatest common divsor come in to play?
https://paste.pythondiscord.com/QQXQ (edit, old stuff removed)
>> Part 1: 0.000243s
>> Part 2: 0.000611s
what is calcer and caculator?
Ah. A holdover.
I thought at first I had to make 2 different methods, and would pass them in.
Thanks for the review 🫡
I'm glad I could accidentally help, lmao
Can I confirm vector //= np.gcd(*vector) is not needed?
I'm confused as to how you can always arrive at the correct answer with that
like, it reduces the dx and dy to the smallest values possible
dx = 2; dy = 4
\/
dx = 1; dy = 2
but if you increment by that amount, wouldn't you get twice as many antinodes?
Yeah that's what I'm confused about too!
it only works if the gcd is always 1 for all the antennas
I believe that is the case, even though it's not explicitly specified
yeah tested on my input, they are all 1
I think it wouldn't work in cases like this
X...........
............
......X.....
............
............
............
............
............
............
............
............
............
That should be just 2 (the antennas themselves), but with the gcd division, it creates 4 antinodes
Imagine the following arrangement:
...O..O...
Without the GCD it'd result in the following antinodes:
#..O..O..#
But with the GCD line it's this:
###O##O###
It's therefore a "more correct" solution by the specification
However, the input is crafted such that the GCD is always 1
So yes, it's not needed
0.00078s
Gotcha! Honestly, I had some trouble understanding the problem statement for day 8. I mostly made sense through the examples. Don't think I still fully understood the spec because that was not what I was expecting to be the correct result.
Day 8 solved in Rockstar, performed by AI: https://youtu.be/ZbV8wVI0bKU
https://github.com/duckyluuk/AoC-2024/blob/main/8/8_lyrics.rock
https://codewithrockstar.com/online
Day eight of 2024's Advent of Code, solved in the Rockstar programming language. Song + thumbnail generated by AI from my written lyrics / code.
Run the code here:
https://codewithrockstar.com/online
(provide the input from https://adventofcode.com/2024/day/8 into the input field)
Full lyrics / code:
The echo is nowhere
The fire is incredible
...
I like my part 1. what do you think?py with open("08.txt") as f: lines = f.read().splitlines() marks = {} for y,row in enumerate(lines): for x,v in enumerate(row): if v != ".": if v not in marks.keys(): marks[v]=[x+y*1j] else: marks[v].append(x+y*1j) antinodes = set() for i in marks.values(): while len(i): val = i.pop() for el in i: antinodes.add(2*el-val) antinodes.add(2*val-el) height = len(lines) length = len(lines[0]) print(sum(True for i in antinodes if length > i.real>=0 and height >i.imag >= 0))
if you're not golfing it (which evidently you're not), why are you trying to compact it so much? use some vertical whitespace 
also could've just summed the actual condition
can you elaborate there?
last go at this one for me:
aoc = {complex(x,y):c for x,r in enumerate(open(filename)) for y,c in enumerate(r) if c>'\n'}
def extend(a,b):
while a in aoc: yield a; a+=b
anod,locs = set(),defaultdict(set)
for a in filter(lambda a:aoc[a]!='.', aoc):
anod |= {an for o in locs[aoc[a]] for an in {*extend(a,a-o)} | {*extend(a,o-a)}}
locs[aoc[a]].add(a)
print(len(anod))
Runs in ~0.002 seconds, so seems good enough. The core trick here is as I encounter a new freq I compare it to all previously seen of that type, and then add it to the ones that have been seen.
(late reply sorry) That time is so small it fluctuates on every run between 0.0006s and 0.0008s, so whatever. 🙂
cleaned it up some more. thoughts? ```py
from collections import defaultdict
with open("08.txt") as f:
lines = f.read().splitlines()
def generateantinodes(v, vector):
firsttime = True
height = len(lines)
length = len(lines[0])
while length > v.real >= 0 and height > v.imag >= 0:
if firsttime:
antinodes.add(v)
firsttime = False
antinodespart2.add(v)
v += vector
marks = defaultdict(list)
for y, row in enumerate(lines):
for x, v in enumerate(row):
if v != ".":
marks[v].append(x+y*1j)
antinodes = set()
antinodespart2 = set()
for i in marks.values():
while len(i):
val = i.pop()
for el in i:
antinodespart2.add(val)
antinodespart2.add(el)
generateantinodes(2val-el, val-el)
generateantinodes(2el-val, el-val)
print(len(antinodes))
print(len(antinodespart2))
i guess i could take out length and height so it doesn't have repeat the len command
You could calulate that up front.
marks = defaultdict(list)
with open("08.txt") as f:
for y, row in enumerate(f.read().splitlines()):
for x, v in enumerate(row):
if v != ".":
marks[v].append(x+y*1j)
height = y
length = x
@winter patio how did u run ada
gnatmake main.adb && ./main < input
on arch it's gcc-ada
❯ gnatmake main.adb
gnatbind -x main.ali
gnatlink main.ali
-macosx_version_min has been renamed to -macos_version_min
ld: warning: ignoring duplicate libraries: '-lSystem'
ld: library 'System' not found
collect2: error: ld returned 1 exit status
gnatlink: error when calling /opt/GNAT/2020/bin/gcc
gnatmake: *** link failed.
yay.
lol
ld: warning: ignoring duplicate libraries: '-lSystem' ld: library 'System' not found
fuckin classic
def part2() -> int:
antennas = parse()
antenna_types = {antenna_type for row in antennas for antenna_type in row} - {"."}
antinode_locations: set[tuple[int, int]] = set()
for antenna_type in antenna_types:
antenna_locations = {
(x, y) for y in range(len(antennas[0])) for x in range(len(antennas)) if antennas[x][y] == antenna_type
}
for first, second in combinations(antenna_locations, 2):
dx = second[0] - first[0]
dy = second[1] - first[1]
newx, newy = first[0], first[1]
while 0 <= newx < len(antennas) and 0 <= newy < len(antennas[0]):
antinode_locations.add((newx, newy))
newx, newy = newx - dx, newy - dy
newx, newy = first[0], first[1]
while 0 <= newx < len(antennas) and 0 <= newy < len(antennas[0]):
antinode_locations.add((newx, newy))
newx, newy = newx + dx, newy + dy
return len(antinode_locations)```
This was less challanging than some of the previous days
I'm just annoyed at the task description being very questionable
the visuals really carried the description for day 8
After updating your model, it turns out that an antinode occurs at any grid position exactly in line with at least two antennas of the same frequency, regardless of distance. no mention of them having to be in intervals here
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics; use Ada.Numerics;
with Ada.Strings.Hash;
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Hashed_Sets;
with Ada.Containers.Indefinite_Hashed_Maps;
with Ada.Containers.Vectors;
with Ada.Numerics.Complex_Types; use Ada.Numerics.Complex_Types;
with Ada.Numerics.Complex_Elementary_Functions; use Ada.Numerics.Complex_Elementary_Functions;
with Ada.Text_IO.Complex_IO;
procedure main is
package C_IO is new
Ada.Text_IO.Complex_IO (Complex_Types);
use C_IO;
function Hash_Complex (z : Complex) return Ada.Containers.Hash_Type is
begin
return Ada.Strings.Hash (z.Re'Image) xor Ada.Strings.Hash (z.Im'Image);
end Hash_Complex;
function Hash_Character (c : Character) return Ada.Containers.Hash_Type is
begin
return Ada.Strings.Hash (c'Image);
end Hash_Character;
package Vector_Locs is new
Ada.Containers.Vectors
(Index_Type => Natural,
Element_Type => Complex);
use Vector_Locs;
package Set_Locs is new
Ada.Containers.Hashed_Sets
(Element_Type => Complex,
Hash => Hash_Complex,
Equivalent_Elements => "=");
use Set_Locs;
package Antenna_Locs is new
Ada.Containers.Indefinite_Hashed_Maps
(Key_Type => Character,
Element_Type => Vector,
Hash => Hash_Character,
Equivalent_Keys => "=");
use Antenna_Locs;
F : File_Type;
File_Name : constant String := "input.txt";
Antennas : Map;
procedure D_Add(M: in out Map; K: Character; V: Complex) is
Vec : Vector;
begin
if not M.contains(K) then
M.include(K, Vec);
end if;
M(K).Append(V);
end D_Add;
X, Y : Integer;
Z : Complex;
function Within (Z : Complex) return Boolean is
Re : Integer := Integer(Z.Re);
Im : Integer := Integer(Z.Im);
begin
return 0 <= Re and Re < X and 0 <= Im and Im < Y;
end Within;
P1_Antinodes, P2_Antinodes : Set;
begin
Open (F, In_File, File_Name);
X := 0;
while not End_Of_File (F) loop
declare
Line : String := Get_Line(F);
begin
Y := 0;
for I in Line'range loop
if Line(I) /= '.' then
Z := (Float(X), Float(Y));
D_Add(Antennas, Line(I), Z);
end if;
y := y + 1;
end loop;
end;
x := x + 1;
end loop;
Close (F);
for Antenna in Antennas.Iterate loop
declare
V : Vector := Antennas(Antenna);
Z1, Z2, D : Complex;
begin
for I in V.First_Index .. V.Last_Index loop
for J in I+1 .. V.Last_Index loop
Z1 := V(I);
Z2 := V(J);
D := Z2 - Z1;
if Within(Z2 + D) then
P1_Antinodes.Include(Z2 + D);
end if;
if Within(Z1 - D) then
P1_Antinodes.Include(Z1 - D);
end if;
while Within(Z2) loop
P2_Antinodes.Include(Z2);
Z2 := Z2 + D;
end loop;
while Within(Z1) loop
P2_Antinodes.Include(Z1);
Z1 := Z1 - D;
end loop;
end loop;
end loop;
end;
end loop;
Put_Line(P1_Antinodes.Length'Image & " " & P2_Antinodes.Length'Image);
end main;
ada is an interesting language
definitely one i might return to someday
of course you do complex
my sol is basically verbatim my python sol
i'm already 3 days behind
which is wonderful when you need to figure out how to like...debug stuff
last two days were quite easy
did you see my recursion from hell?