#Advent of Code 2025
1 messages · Page 2 of 1
because evaluation goes rtl
it's addup(square(subtract(..))
you can write it like that
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)
"under squaring, add up"
it's english
i wonder, is there a fast sorting algorithm that can break out once its seen N sorted elements?
like, rise but with early return
there are so many sorting algorithms
all im doing is sorting
lmfao
so, uhm
is there one that does what I said?
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
this is true
i said i wont go deep into optimizations this year but i wanna try my earlier idea
start with just one per box
hm?
i wonder if you can make a sorting algorithm that sorts a little bit, then outputs the smallest item
thats literally a binary heap
which you can then send to another thread and continue sorting
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
i was thinking run along the entire array, bubble sort it, but also keep the minimum
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
or you can split off sorted items into a channel
but threads won't need to fight for the lock
if you need to access one array from two threads
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
could also use multithreaded sort
you could
were probably at the scale it helps
i mean, you're spending 85% of the time sorting one million entries
it could help here
HUH
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
and this is with parallel sorting instead
insert shitpost here about peek/peak
so no need for my cursed heap of heaps
remove the hash maps also
almost a 1% here
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
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
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
an array is faster than a hash map if your indexes are ints and there's a limited set of them
actually, why did i use a set
also you can type :tm: and discord will replace it with ™
© ® work too, it seems
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
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
now i wonder if my convoluted solution for part 1 will work
nope, i don't have that much ram
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
i can make the "drawing" low-res so the initial search is fast, then work with the high-res version to find the exact size
but that's way more complicated
try to make a list of what disqualifies a rectangle
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 look at this cursed thing
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||
mine does that but way worse
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||
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
n^3 is bigger though
you don't need n^2 time to iterate over pairs of inputs
||```rust
for (i, t1) in red_tiles.iter().copied().enumerate() {
for t2 in red_tiles.iter().skip(i + 1) {
...
}}
well no actually?
n^2 includes two pairs of same two items plus a pair with same item
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)
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
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)
pipe the text+a final , into the read loop,
each pair turns into an array, and the - is removed
seq prints all numbers between the 2 numbers
grep filters for ones that are repetitions
sort|uniq to dedup
paste adds + between each value
bc sums them up
i think i got it all?
how long does it take to run
0.2s on my input/machine
todays part 2 is evil
finally done
you need to
- ||recognize its a linear problem||
- ||solve it||
2 is madness without a library to do it for youj
but theres one problem like this every year
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]||)
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
some sort of dynamic programming?
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
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
add progress bar and leave overnight :3
problem is, it solves the first line in <1ms then hangs on the second line
i don't know for how long yet
uhm
yea cool
alright
i guess i shouldn't do recursive multithreading that much
p much what im doing rn
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. ||
||30% of execution time is clone rn tf||
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
i was thinking of doing that but my cpu fan gets too loud when computing and my mom is sleeping nearby so it'll wait
does rayon manage its threads so they won't go over the os thread limit?
nah I have to find every solution because i wouldn't know if its a global minimum
no, i mean, deeper than that minimum amount of steps
since they need to find the number of steps that's smaller
oh ig yea
it's still very slow
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
machines with 10 lamps
and when i give it a test case with 10 lamps it works
um
i'm getting the weirdest errors on this day, i see
safe rust?
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
oooh
randomized the search order and it's finding solutions now
omg it solved that machine
added caching to not recompute long stuff lol
still day 10 part 2?
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
ok im giving up on figuring out 10p2 myself
||Lecture time!||
||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™||
dead link
it's accessible for me
spoil discussion about this?
yea right
||Im not even close to comfortable with matrices for this||
i learned all that in uni but forgot by now
||Linear Optimization|| is really complicated
and this is not even all of it
||also this will probably not even give the minima right?||
this is just ||how to solve Ax=b||
||so my dumb idea was to add the equation adding up all the coefficients and iterating upward||
you wont finish running that any time soon
wdym?
itll take forever
yes
||or can you do like derivative magic||
not really
||https://en.wikipedia.org/wiki/Triangular_matrix is the notation of a 0 in nomandsland just pad everything here with zeroes?||
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||
||Binary search?||
basically turning it into some weird ||gradient descent||
yes
on the aoc subreddit ive effectively only seen 2 working solutions
- ||use an LP solver library||
- ||extremely optimized BFS with a bunch of heuristics for the order and pruning||
||best way to learn matricies right?||
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||
alright..... that worked i guess..
(btw, there's alot of string cloning and such, i didn't bother doing it without reallocating)
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
oh, i see lol
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
the latter
of course its the same
actually right, should've thought for a bit
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||
that relies heavily on that specific type of input
everyone got it
and that presents are always same size
"same size" as in, fits in 3x3
im pretty sure everyone got external 3x3 internal 7 squares taken
i think i saw ones with 3 empty in my input
huh
anyway
splitting on first group only still works
splitting on any number between them works
because of the ||emptiness||
||```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
};
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)
}
[96..] dam
printed original length-length after present parsing to get that
i got 6 types of presents too so ig everyone does
its always 96 on any input with 6 3x3s
if it changed id just do position(|&c|c==b'x').unwrap()-1 instead
not -2?
if areas got above 99 id need another reverse \n search
width and height are always 2 digit for me
same
need to parse them so you cant just skip
digit count only matters if youre gonna do extreme optimization on the parsing
you can assume they're 2 digits and very unlikely 1
breaks on 1 byte
yea
a whole extra branch to check if its 1 or 2
mark it as unlikely and move on
thats probably actually a meaningful amount of time with how short this is
or store the width in the lookup table
also, replace both -'0' with a single -11*'0'
nah, just fill the table with the space that follows in mind
but then lookup table should do 0 and 0\n
'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
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
couldn't you do it as const block
do what
the table
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
(ignoring link time)
looking into the table is still at runtime obviously
or make a macro that makes a giant match statement instead
how would rust handle that
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
*in this case that you already know how to code a good table
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
this?
match arms were in straight ascending order from 0, not skipping anything
yeah makes sense
second one is just random on both sides
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
what about => unreachable
function with a jump table
to small gadgets that return specific values
probably just a ud2 instead of panic..
ideally it should be able to go back to a normal table
weird choice but okay
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
characters dont matter, its about the amount of asm bytes
i assumed itd be the same but maybe not
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
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
i guess it can estimate size usage for both
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
not 2 lookup tables?
no, jump tables
compiler suc
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
||Ok so I just did 12 and ugh i dislike this so much||
why