#AoC 2024 | Day 8 | Solutions & Spoilers

351 messages · Page 1 of 1 (latest)

magic wasp
#

Language roulette is... Ada!

torpid current
#
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

cosmic sedge
#

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
torpid current
#

Oh yeah I forgot about combinations lol

proven moth
#

I'm glad I didn't have to find the common divisor.

glossy junco
#

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))

winged stream
#

had an off-by-1 error i guess

torpid current
#

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

cosmic sedge
glossy junco
#

i had p1 += is_valid(nx, ny) and was wondering what was off

winged stream
#
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

rich gust
cosmic sedge
#

roughly [int(i) for i in re.findall(r"\d+", x)]

winged stream
#

initially had i = 1 above and didn't see the error

#

honestly the wording of the problem wasn't super clear to me

wind crown
#

today was really easy

wind crown
shrewd flax
#

quick question yall for p2 what would be the answer to an input like

A..
...
..A
torpid current
#

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

torpid current
torpid current
#

But yeah 2

shrewd flax
#

whats stopping it from being in the middle of the two?

#

or did i misread

torpid current
#

Twice the distance from one as from the other

shrewd flax
#

part 2

#

i meant

torpid current
#

Oh yeah lol

shrewd flax
#

could it there be an antinode in the middle?

torpid current
#

I don't think the input would come like that anyway

burnt tartan
#

today was way too easy for day 8

torpid current
#

There definitely aren't d = 0 mod 3 pairs (I found this out because I misinterpreted the test)

fresh prawn
#

another brute force problem :/

sour sparrow
#

fr i feel like in previous years we've had at least 1 non bruteable question by now

shrewd flax
#

well i solved the problem its just i think my code doesnt work like 100% of the time 💀

burnt tartan
hushed cobalt
#

i had a hard time visualizing the logic

sour sparrow
#

part 2 could have been interesting with a much larger map i think

hushed cobalt
#
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))
fresh prawn
hushed cobalt
#

that whole function is for debugging purposes don't mind it

ancient panther
#

what's p2? not at home so can't do it yet

glossy junco
fresh prawn
#

because the grid is tiny

hushed cobalt
sour sparrow
ancient panther
#

ah

shrewd flax
#

oh wait theres gonna be some cool visualizations for today :)))

burnt tartan
hushed cobalt
#

there's probably a smarter way to do this..

tough plover
#

Part 1 done :)

sour sparrow
#

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)
burnt tartan
#

you don't need to check every point for antinode-ness

rich gust
#

I GOT OFF BY ONE ERRORS 5 ||FUCKING|| TIMES TODAY. WASTED 15 MINUTES IN PENALTY

hushed cobalt
#

why didn't i think of that

torpid current
sour sparrow
#

equispaced, i guess

torpid current
#

It's not 'equi' at all, they aren't equal

#

It's just everything on the line lol

hushed cobalt
#

so it's fair to say "equidistant"

fresh prawn
burnt tartan
fresh prawn
#

at least with day6 there was richness with the RTL vs LTR distinction

burnt tartan
glossy junco
#

wait does it also do it with

#

tree

shrewd flax
burnt tartan
#

even the lore for this day feels disconnected from finding the Chief, lol

hushed cobalt
wind crown
#

i lowkey havent been reading the stories at all

ancient panther
hushed cobalt
terse dawn
#

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.

hushed cobalt
wind crown
ancient panther
#

ah

#

ok

winged stream
#

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

tough plover
#

And part two done :)

hushed cobalt
#

i can't believe i had a brain meltdown TT

#

that's it i'm making a vector class

winged stream
#

that's true

#

probably does look better

shrewd flax
wind crown
#

i substituted the while loop for an iter.count and called it a day

hushed cobalt
#

oh well

tough plover
#

Wow today was pretty fast computationally even with way too much hashing ```
8 1 69µs .01% .06%
8 2 131µs .01% .12%

hushed cobalt
#

??? why didn't i use this what

sour sparrow
#

the government doesnt want you to know this but theres a builtin vector class

#

its called complex

hushed cobalt
sour sparrow
#

how dare u

hushed cobalt
#

doesn't have a full precision variant

fresh prawn
#

hm actually, I think today's problem might actually be wrong

hushed cobalt
#

how did you get to that conclusion

fresh prawn
#

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)

sour sparrow
#

do you mean like in terms of real physics or something

tough plover
sour sparrow
#

because we had elephants holding operators yesterday

terse dawn
#

Yeah, Python

fresh prawn
#

the wording is vague, but if we restrict ourselves to the integer grid this is what that would mean

tough plover
#

I'm glad I didn't have any prompt misunderstanding issues. The biggest issue I had was forgetting to sanitize the tests.

sour sparrow
#

hmm yeah i guess

#

the examples make it pretty clear

hushed cobalt
fresh prawn
#

I don't think the examples cover this

#

p2

hushed cobalt
#

the wording is just ambiguous

sour sparrow
#

doesnt this cover it

terse dawn
#

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)

fresh prawn
#

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

wind crown
#

It matters when the gradient of the points is (1, 1)

sour sparrow
#

julia is kinda nice that way because Complex is generic

#

so you can have Complex{Int32} and Complex{Float32}

fresh prawn
#

which include like, an antenna offset of (2,2) and (3,3) etc

gloomy crag
#

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))
spiral gulch
#

same

sour sparrow
#

it would also matter when the gradient is like (k, 0) or (0, k)

fresh prawn
#

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

gloomy crag
#

I did enjoy using combinations(locs,2) here. And I always enjoy using sets.

spiral gulch
#
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
proven moth
spiral gulch
#

the slope thing is repetitive could have avoided that

spiral gulch
#

talking about my code

#

i had to add dx,dy then subtract dx,dy separately

gloomy crag
spiral gulch
#

yeah ic

shrewd flax
winged stream
#
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

weary wing
#

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))
shrewd flax
#

maybe wording could be better for today (i read every 5th word)

shrewd flax
#

nvm

#

thats just path

spiral gulch
#

i did not even read every 5th word

winged stream
#

path is just path

spiral gulch
#

i think i read exactly 20 words

wind crown
tough plover
weary wing
#

Please read the message you just got from the bot 🙂

shrewd flax
#

dang can’t say that

shrewd flax
winged stream
shrewd flax
#

saw*

gloomy crag
winged stream
#

this one line doesn't really need a separate function y, x = o + i * slope

#

i don't think

weary wing
#

I was annoyed at first that the problem didn't define what distance to use, until I realized it doesn't matter

winged stream
#

i could just break slope up into dy, dx and repeatedly add i guess

weary wing
#

yeah it's sad how complex numbers don't work nicely with rectangular grids

winged stream
#
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()
wind crown
#
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))
winged stream
#

there, got rid of the i

gloomy crag
#

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))
winged stream
#

this isn't valid python 😦

while podes[y, x] := inbounds(y, x):
winged stream
#

can only walrus with identifier

weary wing
#

oh

wind crown
#

ah

#

Bummer

pallid bronze
#

I really enjoyed today's puzzle

#

Though I frustrated myself with tons of small typos

#

Ah wel

winged stream
#

my bed time was several hours ago, but i kept myself awake with a pot of coffee

gloomy crag
#

well not via coffee anyway.

winged stream
#

why would it be a good thing

glossy junco
winged stream
#

but it is a thing i did

shrewd flax
#

but yk gotta do it for leaderboard

burnt tartan
#

for once my timezone is rather convenient

winged stream
#

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

hushed cobalt
glossy junco
#

oooo nice

burnt tartan
#

I'm just happy competing with you guys 😁

hushed cobalt
#

i'm happy to have something to do

#

-# i do get jealous sometimes :<

rich parrot
#

competition is good for the market

shrewd flax
winged stream
#

need some competition for visualizations

shrewd flax
#

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

rich parrot
#

goodnight 💋

misty oar
ancient panther
#
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)
misty oar
#

oooh i can use combinations

#

i didn't even think about that

slim herald
#
# 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
burnt tartan
cobalt plaza
#
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!

last pond
cobalt plaza
#

that looks like pycharm

last pond
#

it is pycharm

cobalt plaza
dry marsh
#

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. 💀 🫢

cinder citrus
#

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
cinder citrus
# dry marsh ```py grid_dict = {} for line_no, line in enumerate(data.split('\n')): ...

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)).. pithink why not just set(xs) or like make it a set right away instead of appending and then set()-ing

narrow nebula
#

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))
}
cinder citrus
#

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

narrow nebula
#

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

cinder citrus
#

no such thing in my input either

narrow nebula
#

also I love itertools combinations

cinder citrus
#

eh
its just

 for (i = 0; i < n; i++) {
    a = xs[i]
    for (j = i + 1; j < n; j++) {
        b = xs[j]
    }
}
narrow nebula
#

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

cinder citrus
midnight fiber
rich parrot
#
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

dry marsh
dry marsh
#

ima make a different implementation for p2 i have an idea

jaunty perch
#

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....

jagged sandal
#
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()
cobalt plaza
#

and itertools.combinations

#

those are some thicc names though :kekw:

jagged sandal
#

mmm

#

gotta be descriptive

jaunty perch
cobalt plaza
#

You've earned the title: NeverNester!

jaunty perch
#

should be combinations I switched to permutations when I was getting Part1 wrong out of desperation but I had a bug elsewhere. 😂

cobalt plaza
#

haha

misty oar
glossy junco
elder stirrup
#

maybe i'll revisit and rewrite it later given the time

elder stirrup
mortal sigil
#

both parts

calm oyster
#

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)

narrow nebula
#

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

weary wing
#

It's ok when your map is relatively small

elder stirrup
#

i mean, what is a 2D array, if not a fixed-size hashmap with 2D coordinate keys? :P

sour sparrow
#

hello lua

elder stirrup
#

i was about to errm acktually you but no you're right, tho only specifically in that "arrays are just hashmaps" direction

calm oyster
#

It also helps when you have things like irregularly shaped maps.

narrow nebula
#

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

leaden vessel
#
#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

lean meadow
#

wow I was really surprised it was very simple to go from part 1 to part 2

dim oxide
#

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
winter patio
#

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)

jagged sandal
winter patio
#

right, i realized in the meantime, working on it, thx

steep trench
#

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))
sterile raft
#

I really feel like today could be formularized but i aint smart enough to do that and it runs in .02 secs so 🤷‍♀️

dim oxide
#

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?

silver yew
jagged sandal
#

what is calcer and caculator?

silver yew
#

Ah. A holdover.
I thought at first I had to make 2 different methods, and would pass them in.

#

Thanks for the review 🫡

jagged sandal
#

I'm glad I could accidentally help, lmao

dim oxide
jagged sandal
#

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?

dim oxide
#

Yeah that's what I'm confused about too!

jagged sandal
#

it only works if the gcd is always 1 for all the antennas

burnt tartan
#

I believe that is the case, even though it's not explicitly specified

dim oxide
#

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

steep trench
#

It's therefore a "more correct" solution by the specification

#

However, the input is crafted such that the GCD is always 1

steep trench
dim oxide
edgy raptor
#

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
...

▶ Play video
fickle rune
#

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))

jagged sandal
#

if you're not golfing it (which evidently you're not), why are you trying to compact it so much? use some vertical whitespace waaaaaaaaaahhhhhh

#

also could've just summed the actual condition

fickle rune
gloomy crag
#

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.

silver yew
# mortal sigil 0.00078s

(late reply sorry) That time is so small it fluctuates on every run between 0.0006s and 0.0008s, so whatever. 🙂

fickle rune
#

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(2
el-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

gloomy crag
ancient panther
#

@winter patio how did u run ada

winter patio
#
gnatmake main.adb && ./main < input
ancient panther
#

but where do i get gnatmake

#

oh nvm i just can't read

winter patio
#

on arch it's gcc-ada

ancient panther
#
❯ 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.

last pond
#

lol

#

ld: warning: ignoring duplicate libraries: '-lSystem' ld: library 'System' not found

#

fuckin classic

keen crystal
#
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
winter patio
#

I'm just annoyed at the task description being very questionable

elder stirrup
#

the visuals really carried the description for day 8

keen crystal
#

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

ancient panther
#
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

ancient panther
winter patio
#

oh, you're taking the easy route 🙃

#

I write in the roulette lang directly

ancient panther
#

i'm already 3 days behind

winter patio
winter patio
ancient panther
#

day 9 was not

#

i struggled with day 9 in python

winter patio
#

did you see my recursion from hell?

ancient panther
#

i did not

#

could you link it

#

can't be bothered to scroll