#Advent of Code 2025

1 messages · Page 2 of 1

wild otter
#

why did the n_2 move to the right

smoky owl
#

it's addup(square(subtract(..))

wild otter
#

okay but it makes sense now and didnt before

#

why isnt euc distance just sqrt(this)

smoky owl
#

but it misses the phenomenal symmetry that the formula has

#

distance is "under square, add"

#

square, add, sqrt

#

the first version reflects that

#

(with the subtraction at the front because we're doing two arbitrary points instead of doing it to the origin)

smoky owl
wild otter
#

very slightly faster

#

as expected, almost all the time is spent in quick_sort

smoky owl
#

like, rise but with early return

wild otter
#

all im doing is sorting

smoky owl
smoky owl
wild otter
#

i didnt really understand your idea

#

oh

#

a largest k algorithm?

#

to get just the closest 1000?

#

there are some largest k algorithms, yes

#

not that useful in part 2

smoky owl
wild otter
#

i said i wont go deep into optimizations this year but i wanna try my earlier idea

#

start with just one per box

smoky owl
zealous jungle
#

i wonder if you can make a sorting algorithm that sorts a little bit, then outputs the smallest item

wild otter
#

thats literally a binary heap

zealous jungle
#

which you can then send to another thread and continue sorting

wild otter
#

ok not that

#

but you could probably write something that uses a binary heap while sorting an array in the background

#

and hot swap them

#

sounds like a pain

zealous jungle
#

i was thinking run along the entire array, bubble sort it, but also keep the minimum

wild otter
#

hmm

#

i can imagine an insertion sort that has an atomic counter for how much is already sorted

#

so the thread thats reading the sorted array pauses when there isnt a sorted item to read

#

but that means im overlapping the reading, that takes like 5%
with sorting

zealous jungle
#

or you can split off sorted items into a channel

wild otter
#

that will grow from nlog n to n^2

#

sure, but thats even slower

#

hmm

zealous jungle
#

but threads won't need to fight for the lock

wild otter
#

what lock

#

no locks

#

just atomic counter

zealous jungle
#

if you need to access one array from two threads

wild otter
#

you dont need a lock

#

in rust youll probably need some unsafe cell but its fine

#

as long as you maintain that any item below the current counter will never be modified its safe

#

for how to do that..

#

you could modify some nlogn sorting algorithm to prefer sorting the start first

#

radix/quick sort would work

#

merge no

zealous jungle
#

could also use multithreaded sort

wild otter
#

you could
were probably at the scale it helps

zealous jungle
#

i mean, you're spending 85% of the time sorting one million entries

#

it could help here

wild otter
#

so i use a heap of heaps

#

each internal heap is a box and its distances to other boxes

#

and the heaps are sorted in the outer heap based on their peek value

wild otter
smoky owl
wild otter
#

so no need for my cursed heap of heaps

zealous jungle
#

remove the hash maps also

zealous jungle
wild otter
#

those are the sets

#

each circuit is a hash set

#

of box ids

zealous jungle
#

i did it with just vecs and it was faster

#

there are limited circuit ids

wild otter
#

this laptop isnt stable enough to measure 1% saving

#

||```rs
fn boxes_to_distances(boxes: &[(i32, i32, i32)]) -> BinaryHeap<DistancesFromBox> {
let distance_count = (boxes.len() * (boxes.len() + 1)) / 2;
let mut distances = BinaryHeap::with_capacity(distance_count);
for (idx1, box1) in boxes.iter().enumerate() {
let mut distances_from_box = DistancesFromBox {
box_id: idx1 as u32,
distances: BinaryHeap::with_capacity(boxes.len() - idx1),
};
for (idx2, box2) in boxes[idx1 + 1..].iter().enumerate() {
// correct the offset
let idx2 = idx2 + idx1 + 1;
distances_from_box.distances.push(Distance {
box2: idx2 as u32,
distance: calc_distance(*box1, *box2),
});
}
distances.push(distances_from_box);
}
distances
}
...
let mut this_box = distances
.pop()
.expect("There must be at least 1000 possible connections");
let other_box = this_box.distances.pop().unwrap();
let mut circuit1_id = box_to_circuit[this_box.box_id as usize];
let mut circuit2_id = box_to_circuit[other_box.box2 as usize];
distances.push(this_box);
...

#

my heap of heaps code

#

ignore the expect forgot to remove

zealous jungle
#

all my structures are [bool; n*n] for connections, [usize; n] to map junction to circuit id, [Vec<usize>; n] for circuit id to vec of junction ids

wild otter
#

yeah i know how to do it with hash maps

#

just saying i wouldnt be able to measure if its faster

#

i could if i setup the laptop with a lower clock and put it back on the stability fan ^tm

zealous jungle
#

an array is faster than a hash map if your indexes are ints and there's a limited set of them

wild otter
#

actually, why did i use a set

zealous jungle
#

also you can type :tm: and discord will replace it with ™

wild otter
#

#

huh

zealous jungle
#

© ® work too, it seems

wild otter
#

removed the hash, happy?

#

is there a better way to do this?

            if circuit2_id < circuit1_id {
                // make id 1 the smallest for the split
                swap(&mut circuit1_id, &mut circuit2_id);
            }
            let (s1, s2) = circuits.split_at_mut(circuit2_id as usize);
            let mut circuit1 = &mut s1[circuit1_id as usize];
            let mut circuit2 = &mut s2[0];
#

i want to take 2 mutable references from the vector

#

i know that they are always distinct

#

its checked one line up

#

its all rayon now

#

4.45% on parsing and filling the vector before the sort

wild otter
#

todays part 2 is really hard

#

part 1 was among the easiest so far

#

and then this massive ramp up

#

I DID IT

#

visual of part 2, maybe spoiler

wild otter
zealous jungle
#

now i wonder if my convoluted solution for part 1 will work

#

nope, i don't have that much ram

zealous jungle
#

only solution i see so far is to draw the shape an a 2d grid, fill it and then search max rectangles there walking on the edge

#

but that will take forever to write and to run

#

@wild otter no way you did that though, right?

#

i don't know how to solve it differently so whatever, i won't do the second part

zealous jungle
zealous jungle
#

but that's way more complicated

wild otter
#

try to make a list of what disqualifies a rectangle

zealous jungle
zealous jungle
#

oh wow

#

whatever

#

it works

#

a bit faster

#

(i store the "drawing" in chunks, and added skipping empty chunks to not check everything in them)

wild otter
#

disgusting

#

👍

#

the real answer i was looking for is || for every potential rect, check if no consecutive pair of points draws a line that crosses into the rect||

zealous jungle
#

mine does that but way worse

wild otter
#

my specific solution was ||rect is invalid if one of two things is true: 1. any point is within the rect, 2. the line between any 2 consecutive points intersects with the diagonal of the rect||

zealous jungle
#

smart

#

O(n*((n*n)/2)) solution, i think

wild otter
#

we call that O(n^3)

#

yes

#

i took an optimization from someone i talked to of calculating all the areas first, sorting, and checking from biggest until i find a valid one

zealous jungle
#

you don't need n^2 time to iterate over pairs of inputs

wild otter
#

you need n time per pair

#

there are n^2 pairs

zealous jungle
#

||```rust
for (i, t1) in red_tiles.iter().copied().enumerate() {
for t2 in red_tiles.iter().skip(i + 1) {
...
}}

wild otter
#

yeah we have n^2 of these

#

i mean

zealous jungle
wild otter
#

the inner part runs n^2 times

#

O(n^2) pairs

zealous jungle
#

n^2 includes two pairs of same two items plus a pair with same item

wild otter
#

i think you dont understand how big O notation works

#

it removes all the smaller factors

#

50n = O(n)

#

(n^2)/2 = O(n^2)

#

n^3+n^2=O(n^3)

zealous jungle
#

then it's just an arbitrary thing

#

not reflective of the real algorithm

wild otter
#

not sure what youre talking about
thats how big O works

#

the goal is to reflect how it scales when the parameters grow

#

if you dont care about the scaling just dont use big O

#

its very useful to compare algorithms at a high level

#

because the scales were working on are usually past the point where performance graphs would diverge

#

insertion sort does (n(n+1))/2 comparisons and very little else, and quick-sort does, idk (30*n*log n) operations

#

i dont really need the extra stuff to know that at the scales i care for quick-sort wins

#

so instead, quick sort O(nlogn) and inseration is O(n^2), easy to see quick sort wins

white talon
#

day 2 part2:
|| I had the fun realization that the prime finding regex would work here so I came up with this

echo ',' | cat input - | while read -a arr -d ,; do seq $(echo $arr | sed 's/-/ /') | grep -P '^(.+)\1+$'; done | sort | uniq | paste -sd+ | bc```||(Im not a bash programmer)
wild otter
#

how long does it take to run

#

0.2s on my input/machine

zealous jungle
#

same

#

wow

wild otter
#

thats surprisingly fast

#

only 6 times slower than my solution

wild otter
#

todays part 2 is evil

#

finally done

#

you need to

  1. ||recognize its a linear problem||
  2. ||solve it||
#

2 is madness without a library to do it for youj

#

but theres one problem like this every year

zealous jungle
#

okay, the dumbest solution to the part 1 isn't that slow

#

(||make a [Vec<u16>; max_states] to track transitions between states, make a vec with distances to the final state, update it while it updates, read distances[0]||)

zealous jungle
#

for the part 2 my only idea is ||depth-first search on values||

#

but i already screwed up that in the first part and had to do a dumb but slow solution

wild otter
#

my part 1 is just a stupid brute force

#

||recursively go down the button list and xoring the state||

#

and it was plenty fast

#

O(2^buttons) per line but there are like 10 buttons max

zealous jungle
#

oh no

#

i made a program trhat solves part 2 test input correctly but on the real input it just hangs

#

oh nooo

#

it's just taking forever, it's not hanging

smoky owl
zealous jungle
#

problem is, it solves the first line in <1ms then hangs on the second line

#

i don't know for how long yet

#

yea cool

#

alright

#

i guess i shouldn't do recursive multithreading that much

white talon
#

im trying to figure out something smart

#

like where Im at rn is ||Mathematically you can express each button as a vector with unit distances in the directions corresponding to the light dimension. Well yeah that was during math class.

anyways, my current impl is||

#

thanks ducks!

#

anyhow thats not that much faster. anyhow my current impl is ||store each button associated with its lowest indexed light, and iterating over each combination which satisfies the current light, and since non of the buttons in my table can modify backwards i go all the buttons like this. Unfortunately, this doesn't guarantee that I find a minima, and i must therefore find every solution and find the minimum thereof. ||

white talon
#

||30% of execution time is clone rn tf||

zealous jungle
#

part 2?

#

also to find the minimum, you can have it in a AtomicUsize and read that from all searching threads

#

so as soon as one thread finds new minimum value, other threads will stop wasting their time going deeper

zealous jungle
#

does rayon manage its threads so they won't go over the os thread limit?

white talon
zealous jungle
#

no, i mean, deeper than that minimum amount of steps

#

since they need to find the number of steps that's smaller

white talon
#

oh ig yea

zealous jungle
#

running for 5 minutes on... 5 threads? and still haven't found a solution for line 2

#

my thread spawner thingy not spawning more than 5 threads for some reason

#

WHAT

#

i'm sorry, it segfaulted??

#

weird

#

it can't solve 10 lamp specifically

#

anything else it can solve

white talon
#

10 lamp?

#

10 lamp gotcha

zealous jungle
#

machines with 10 lamps

#

and when i give it a test case with 10 lamps it works

#

i'm getting the weirdest errors on this day, i see

white talon
#

safe rust?

zealous jungle
#

yes, all safe rust

#

oh wow

#

it solved one 10 lamp machine i threw at it

#

so it works, it's just that machines are hard

#

alright, i guess i'll give it some time

#

no, it just dies

#

after running for 9 minutes it segfaults

#

oh god, joltage can be above u8

zealous jungle
#

oooh

#

randomized the search order and it's finding solutions now

#

omg it solved that machine

zealous jungle
#

added caching to not recompute long stuff lol

zealous jungle
#

oh no, it almost OOM'd my pc

#

whatever, enough of this shitshow

wild otter
wild otter
#

i did say its one of those "you better use a library" days

#

btw warning: day 11 is one of those with a different example in part 2

#

was stuck for a bit before i noticed i need to change the example input

white talon
#

ok im giving up on figuring out 10p2 myself

#

||tbf I do have a working algo which is almost done (and hopefully correct)||

#

||I gave up tbh, I just slapped threading on it and its faster™||

white talon
#

really?

#

works for me

wild otter
#

is it from your university?

#

probably does some authentication

white talon
#

no

#

just a random uni

smoky owl
#

it's accessible for me

wild otter
#

oh now it loaded

#

weird

white talon
#

spoil discussion about this?

#

yea right

#

||Im not even close to comfortable with matrices for this||

wild otter
#

and this is not even all of it

white talon
#

||also this will probably not even give the minima right?||

wild otter
#

this is just ||how to solve Ax=b||

white talon
#

||so my dumb idea was to add the equation adding up all the coefficients and iterating upward||

wild otter
#

you wont finish running that any time soon

white talon
#

wdym?

wild otter
#

itll take forever

white talon
#

nah I have 144 done already

#

||(been running for hours so lol)||

zealous jungle
white talon
wild otter
#

not really

white talon
wild otter
#

you could ||use the missing amounts to click multiple times to save on iterations||

#

but that gets complicated and you need to deal with a lot of things like ||overshooting||

white talon
#

||Binary search?||

wild otter
#

basically turning it into some weird ||gradient descent||

wild otter
#

on the aoc subreddit ive effectively only seen 2 working solutions

#
  1. ||use an LP solver library||
  2. ||extremely optimized BFS with a bunch of heuristics for the order and pruning||
white talon
#

||best way to learn matricies right?||

zealous jungle
#

that doesn't look right lol

#

TOO LOW??

#

waity

#

what

#

it gives a different answer every time it's ran

#

uh oh

#

and it ||loops back through devices, that's not right too||

#

it ||loops back through you, which would've been caught in the first part||

zealous jungle
#

alright..... that worked i guess..

#

(btw, there's alot of string cloning and such, i didn't bother doing it without reallocating)

wild otter
#

day 12 sucks

#

not because it is extremely hard

#

but because it is extremely hard if you dont make the massive assumption you need to

#

my code solves the real input and not the example because of how big this assumption is

#

and its not obvious even if you do look at your input

zealous jungle
#

though i haven't finished 10.2 so that's the end for me

#

i did wonder ||how many trees can be solved without actually solving them, just by doing the math||

#

||though i also wonder if you do write the correct solver, will it give the same results or not||

#

does 12.2 exist or you just solve all previous parts and it gives you the last star

zealous jungle
#

actually right, should've thought for a bit

wild otter
#

if you split the trees into ||"always fits with any packing", "will never fit even with perfect packing" and "dunno solve np-hard"||
that last group is always empty

#

so i only split based on the first

#

dont even need to parse the presents

#

||filter(|area| area.size>=area.presents*9).count||

zealous jungle
#

that relies heavily on that specific type of input

wild otter
#

everyone got it

zealous jungle
#

and that presents are always same size

wild otter
#

"same size" as in, fits in 3x3

#

im pretty sure everyone got external 3x3 internal 7 squares taken

zealous jungle
#

i think i saw ones with 3 empty in my input

wild otter
#

sure its not the example?

#

the example has one like that

#

the example is stupid

zealous jungle
#
2:
#..
##.
.##
#

from my input

wild otter
#

huh

#

anyway
splitting on first group only still works

#

splitting on any number between them works

#

because of the ||emptiness||

zealous jungle
#

||```rust
let result = 'solve: {
let total_sum: usize = counts.iter().copied().enumerate().map(|(i, c)| presents[i].active_tiles * c).sum();

if total_sum > width * height {
    break 'solve Some(false);
}

let width_fit = width / PRESENT_SIZE[0];
let height_fit = height / PRESENT_SIZE[1];
let total_presents: usize = counts.iter().copied().sum();

if width_fit * height_fit >= total_presents {
    break 'solve Some(true);
}

None

};

wild otter
#

yeah thats kinda what i started with

#

just that i had a todo at the end

#

which is never hit

#

i then simplified to this
||```rs
pub fn part_one(input: &str) -> Option<u64> {
let mut valid = 0u64;
let mut input = &input.as_bytes()[96..];
while !input.is_empty() {
let (width, rem) = fast_parse::<u32>(input);
let (height, rem) = fast_parse::<u32>(&rem[1..]);
input = &rem[2..];
let mut presents_to_fit = 0;
for _ in 0..6 {
let (num, rem) = fast_parse::<u32>(input);
input = &rem[1..];
presents_to_fit += num;
}
if width * height >= presents_to_fit * 9 {
valid += 1;
}
}
Some(valid)
}

zealous jungle
#

[96..] dam

wild otter
#

printed original length-length after present parsing to get that

zealous jungle
#

i got 6 types of presents too so ig everyone does

wild otter
#

its always 96 on any input with 6 3x3s

#

if it changed id just do position(|&c|c==b'x').unwrap()-1 instead

zealous jungle
#

not -2?

wild otter
#

hm

#

actually yeah 2

zealous jungle
#

or actually, just search back from x to the nearest \n

#

it's at most 2 bytes

wild otter
#

if areas got above 99 id need another reverse \n search

zealous jungle
#

width and height are always 2 digit for me

wild otter
#

same

zealous jungle
#

and present count are almost always 2 digit

#

singular one digit

wild otter
#

need to parse them so you cant just skip

#

digit count only matters if youre gonna do extreme optimization on the parsing

zealous jungle
#

you can assume they're 2 digits and very unlikely 1

wild otter
#

could replace /10 parsing with a single lookup table

#

with that assumption

zealous jungle
#

(bytes[0] - b'0') * 10 + (bytes[1] - b'0')

#

or a lookup yea

wild otter
#

breaks on 1 byte

zealous jungle
#

yea

wild otter
#

a whole extra branch to check if its 1 or 2

zealous jungle
#

mark it as unlikely and move on

wild otter
#

thats probably actually a meaningful amount of time with how short this is

zealous jungle
#

or store the width in the lookup table

wild otter
wild otter
zealous jungle
#

but then lookup table should do 0 and 0\n

wild otter
#

'1 ' is 1

#

'1\n' is also 1

#

yeah what you said

#

i did -99.9 to 99.9 parsing with a big lookup table in 1brc

#

took quite a bit of the CPU cache

zealous jungle
#

lookup[u16::from_be_bytes(bytes) - 0x300a]

#

i think

wild otter
#

you dont need to do hex stuff

#

11*b'0' is a constant that will computed in compile time

#

but also technically

#

you could skip the subtraction

#

if youre willing to waste that many slots

#

never accessed, not in cache
all you wasted is a tiny bit of memory

#

for my big table i used pext to construct the index from bits

#

i used pext like 4 times in aoc this year

zealous jungle
#

couldn't you do it as const block

wild otter
#

do what

zealous jungle
#

the table

wild otter
#

fill the table?

#

of course

#

my compile time on 1brc is kinda long for the amount of code there

#

because its filling the table with the const evaluation thing

wild otter
#

looking into the table is still at runtime obviously

zealous jungle
#

how would rust handle that

wild otter
#

a match statement is either a bunch of ifs, or a lookup table

#

if you want max perf, dont trust the compiler to choose the right one

#

especially when the indexing is complicated

#

also thats a fuckton of code

wild otter
zealous jungle
#

yea alright, it makes a lookup table

#

oh sheeeesh

#

if input numbers are random, it makes a huge function instead

#

and instead of a lookup table, it does this

wild otter
#

what did you put as input

#

nvm you show

zealous jungle
#

random numbers

#

sampled from my aoc input lol

wild otter
#

what changed between the 2

#

like

#

the first looks kinda random already

zealous jungle
wild otter
#

yes

#

just in order 1 to whatever with values to return?

zealous jungle
#

match arms were in straight ascending order from 0, not skipping anything

wild otter
#

yeah makes sense

zealous jungle
#

second one is just random on both sides

wild otter
#

making a table from random is hard

#

maybe the range was too big or something

#

im guessing with a shorter range itll work

#

even with a few holes?

#

what does the ascending one do if you poke holes in it

#

or reorder

zealous jungle
#

lookup table with 0s

#

(with _ => 0 in match)

wild otter
#

what about => unreachable

zealous jungle
zealous jungle
#

to small gadgets that return specific values

wild otter
#

but still a lookup table?

#

wait what

#

show

zealous jungle
#

lookup table to instruction addresses that return values

wild otter
#

weird

#

what about unreachable unchecked

zealous jungle
#

probably just a ud2 instead of panic..

wild otter
#

ideally it should be able to go back to a normal table

zealous jungle
#

yea, it does

#

except now it's 2 instead of 0 in the holes

wild otter
#

weird choice but okay

zealous jungle
#

fair dice roll lol

#

no, it's just always 2

#

oh wait i'm blind

#

it's .zero 2

#

i guess that's one character shorter than .short 0 ok

wild otter
#

characters dont matter, its about the amount of asm bytes

#

i assumed itd be the same but maybe not

zealous jungle
#

it's probably same

#

it must be same actually

wild otter
#

are data sections just copied or are they instructions how to write the real data section

#

like
if i write .zero 10000 it could be like 16 bytes worst case and generate 10000 bytes

#

if its instructions

zealous jungle
#

i think objects contain full data sections

#

also at about 350 entries in the lookup table it decides to replace it with a jump table

#

ah, it's about how far the items in it are apart

#

not about its size

wild otter
#

yeah i guessed earlier

#

gaps

#

it has some heuristic to when to do a table

zealous jungle
#

i guess it can estimate size usage for both

wild otter
#

hmm

#

what if you have 2 dense ranges with 1 big gap between?

zealous jungle
#

jump table long per item + mov + ret

#

jump table

#

with a big gap inbetween that points to nothing code

#

but i should check with a huge gap

#

with a gap of about 1000000000 it makes 2 jump tables

wild otter
#

not 2 lookup tables?

zealous jungle
#

no, jump tables

wild otter
#

compiler suc

zealous jungle
#

even though in those dense ranges, my values are consecutive

#

that's 3 bytes for each code block and 8 bytes for the jump table value

#

and could've been just 2 bytes for the actual value

white talon
#

||Ok so I just did 12 and ugh i dislike this so much||

zealous jungle
#

why