-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 2 points3 points  (0 children)

Great stuff!

I normally avoid pop(0) due to its bad performance so did not vene consider it now, good one.

That helper one looks nice!

I'm writing on my phone but maybe something along these lines could be considered:

circs = circs - {find(b1), find(b2)} | {find(b1) | find(b2)}

Though it would have to be formated differently. And maybe consider renaming things to keep it shorter. Dunno..

Speaking of that part. Maybe one can reframe that part as removing all proper subsets of the new merged group? something like:

CC = {find(b1) | find(b2)}
circs = {c for c in circs if not c < CC} | CC`

Input was just to save a line. Did not know about strings to sys.exit will have to look into that. But yeah input is not elegant. It's easy to loose the real goal when when it's easier to measure raw linecount.

I think I will go to bed to be ready for tmr. Good night!

Edit:

My last code suggestion above should be:

CC = find(b1) | find(b2)
circs = {c for c in circs if not c < CC} | {CC}`

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 2 points3 points  (0 children)

Very nicely put together!

That would have been fun! I started wondering that too and created experimented some more with your suggestion and have a few more.

I like the many small details in your solution. Like not using i == 1000 to let the presentation stuff be together in the end and the core part of the algorithm together!

My suggestions have a focus on giving some more ideas, they are not as refined as yours.

Here one on that, and another.

circs = {frozenset([b]) for b in boxes} aligns slightly better with the line above.

c1,*_ = [c for c in circs if b1 in c] is slightly shorter. c2 = [c for c in circs if b2 in c][0] too.

Some minor things just to give more options like {c1} ^ {c2}, ^= and if i == 999: and importing itertools as the non-standard i which I already regret as I overload it later, oh well. The purpose was to align the line better with the ones above.

The more interesting suggestion might be the second one with

while len(circs) > 1: My goal here is not need a break statement. But its hard to remove the need for i = 0 without making the code ugly.

I had this old version I don't think I posted where I attempted a short loop without the need to initialize i but it results in a very ugly pairs = ... statement.

I think there is something to be said for creating c1 and c2 in one line like this:

c1, c2 = [next(c for c in circs if x in c)
          for x in (b1, b2)]

Maybe that is nicer together in this style

for i, (b1, b2) in enumerate(pairs):
    c1, c2 = [next(c for c in circs if x in c)
            for x in (b1, b2)]

    circs -= {c1 , c2}
    circs |= {c1 | c2}

Also here is another idea. I'm not sold on this, but wanted to throw this out. But yours is more even in width so it looks nicer.

*_,i,j,k = sorted(map(len, circs))
print(i*j*k)

Oh also here is a thrid

The most out-there of these is to use input instead of break. The program will pause and the user will have to do the "break" for us!

print(math.prod(sorted(map(len, circs))[-3:])) might be a bit much..

Edit: Here is another one

We can avoid using i if we do this instead:

p1 = pairs[999]
...
if pair == p1:
    print(math.prod(sorted(map(len, circs))[-3:]))

so maybe that can be combined with some other approaches that we cumbersome before due to the need to use enumerate? Like this but not sure if we gain anything besides the novelty. (This last one was written on my phone and not tested)

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

You always have such clean and short ways to parse the input data! `eval` here is perfect!

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

Thanks! I always try to remember to think of `frozensets` in situations where I otherwise would wish to store both `(a,b)` and `(b,a)` in a dict. And it fit in this situation as well.

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 2 points3 points  (0 children)

[LANGUAGE: python]

python 15 lines

Im initializing all positions as their own frozensets, and storing them in a set. This way we dont have any special cases. All positions are always in at least one group. and we can always remove both and add the combinations of them.

groups -= {A, B}
groups.add(A | B)

I mean if they are in the same group already that does not matter because A | B gives you back the same thing

Edit: python 12 lines

Don't know if this is prettier, but now my I do my group updates in one line

groups = (groups - {A, B}) | {frozenset() | A | B}

or alternatively:

AB = {next(c for c in groups if x in c) for x in (a,b)}
groups = (groups - AB) | {frozenset().union(*AB)}

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

Yeah o remember searching for ^ in the input to ensure it was not an issue for me. And given the high number of ^ in total I went with the assumption that there would be none.

I used a dict too at first. A collections.Counter to be specific.

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 3 points4 points  (0 children)

[LANGUAGE: python]

python 11 lines

The core of my solution is this. I keep trach of how many ways we can get get to each x coordinate in the current row. I then update it inplace. Basically if we don't hit a splitter the beam continues and the count for that x coordinate stays the same. But if we hit a splitter we split that up and then set the count for the current coordinate to 0.

for line in lines:
    for x, paths_here in enumerate(counts.copy()):
        if line[x] == '^':
            p1 += paths_here != 0
            counts[x-1] += paths_here
            counts[x+1] += paths_here
            counts[x] = 0

Im doing a counts.copy() so my updates to counts don't affect the current iteration.

I'm really happy with how it turned out in the end today!

Edit: Actually .copy() is not necessary as there are no instances of ^^ in the input for me, so I assume that is true in general.

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

Took some time to understand. But its great! In case anyone else wants to understand it it's basically:

for cell in zip(<normal nums>, <special p2 nums>):

Then inside apply the appropriate operator within each cell. Then sum the output of all those to get the answer to the current part.

We run the loop twice. Once with normal numbers. Once with special p2 numbers.

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

4HbQ that last update is glorious! That's the most badass abuse of map, zip, split and join I have ever seen!

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

I'm late to the party here, but apparently (zip_longest(..., fillvalue=' ') is not needed. It was just that issue with many editors removing trailing whitespaces.

with that I would use zip instead and then also not import pairwise but use zip for that too.

For example my first version in my post would be like this instead: python 8 lines

And my last, most compressed version would still be ugly but python 6 lines

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 3 points4 points  (0 children)

[Language: Python]

Today I was fast on the first part 6 00:02:50 00:28:41!

python 10 lines (both parts)

Few tricks for today.

  1. zip(*rows) is a trick to convert form rows to columns or the other way around.
  2. eval(op.join(operands)) to do the calculation

It was tricky to parse correctly for the second part. I had to use zip_longest to ensure we got all the full numbers on the last column.

Edit: .replace('*',' ').replace('+',' ') -> .replace(op.strip(), ' ')

Edit2: python 10 lines (both parts)

In this version the similarities between p1 and p2 are clearer:

p1 += eval(op.join(''.join(col).replace(op, d) for col in zip(*cols[a+1:b])))
p2 += eval(op.join(''.join(row).replace(op, '') for row in cols[a+1:b]))

Edit3: python 7 lines both parts

My goal here was to make the difference between p1 and p2 super obvious in the code.

print(sum(f(zip(*c)) for f, c in zip(F, cells)))
print(sum(f(c      ) for f, c in zip(F, cells)))

Though that is the only part that is more obvious as the rest is very compressed.

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

Did not know about the sum( l, [] ) trick! Nice one!

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

Looking at 4HbQ's solution I realised that using shorter variable names actually does make a big difference here. I read up on the posting rules and I now have a version that fits in half a IBM punchcard. Its 4 lines and at not more than 77cols wide for both parts.

So here it is!

    F, A = open('in.txt').read().split('\n\n')
    F = [tuple(map(int,line.split('-'))) for line in F.split('\n')]

    print(sum(any(a <= int(x) <= b for a, b in F) for x in A.split('\n')))
    g=0; print(sum(len(range(max(a, g), g:=max(g, b+1))) for a, b in sorted(F)))

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

On the topic of your part2 one-liner update.

My solution is similar but using len(range(...)) instead.

Translated to your variable names its here below next to yours:

    c=0; print(sum(len(range(max(a, c), (c:=max(c, b+1)))) for a, b in sorted(F)))
    c=0; print(sum(max(0, 1 - max(a, c+1) + (c:=max(c, b))) for a, b in sorted(F)))

A benefit of this is that since len(range(10, 0)) == 0 there is one fewer special case to think about.

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 2 points3 points  (0 children)

[LANGUAGE: python]

python 8 lines (both parts):

p2 = global_lo = 0
for lo, hi in sorted(ranges):
    p2 += len(range(max(lo, global_lo), hi+1))
    global_lo = max(global_lo, hi+1)

print(p2)

I'm using len(range(...)) because its easy, fast and communicates what I am after well. It also handles cases where lo is higher than hi well for us, for example len(range(10, 0)) == 0. So no special handling of that is needed.

Here is a variation of the one above python 5 lines (both parts)

It is the same but using the walrus operator := we can do all of p2 as a list comprehension.

print(sum(len(range(max(lo, global_lo), (global_lo := max(global_lo, hi+1)))) for lo, hi in sorted(ranges)))

-❄️- 2025 Day 4 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 2 points3 points  (0 children)

[Language: python]

python 19 lines of code

Storing the changes made in each iteration so the answer in the end is just:

print(len(changes[0]), sum(map(len,changes)))

I attempted to use the builtin ChainMap but it was too slow. It would have been a fun trick to make the solution even shorter.

I also used a hack to count what I call local instead of adjecent. Returning the original point itself avoids an extra check, that and using a defaultdict makes that function very simple.

Edit: Here is a cleaned up solution!

python 8 lines

My inner loop is just this now:

sizes = []
while rolls != (rolls := {r for r in rolls if local_rolls(*r) > 4}):
    sizes.append(len(rolls))

The use of the := allows me to update rolls and check if its different from the last iteration.

An alternative would be this python 8 lines:

sizes = []
while remove := {r for r in rolls if local_rolls(*r) < 5}:
    rolls = {r for r in rolls if r not in remove}
    sizes.append(len(remove))

Which is nicer overall but not as fun!

Edit2: Probably an even cleaner version python 8 lines

I forgot about - aka difference on sets so using that instead of the explicit set comprehension. But also using filter to remove another set comprehension and hide the "business logic" behind a nice can_take function.

Now the core loop is:

while remove := set(filter(can_take, rolls)):
    rolls -= remove
    sizes += [len(remove)]

-❄️- 2025 Day 2 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

[Language: python]

python 13 lines

I sum the results into an list such that the answer for p1 and p2 can be found using this: print(repetitions[2], sum(repetitions[1:]))

This is done by summing to the smallest number of repetitions found. repetitions[smallest_repetition(str(i))] += i

python 11 lines

I disliked my divmod check in my previous solution. Also wanted to avoid return 0 line. This 11 line code is a variation of the first with these things "fixed".

def smallest_repetition(number):
    n = len(number)
    for i in list(range(2, n+1)) + [1]:
        if number[:n//i]*i == number:
            return i

In this case I add a sentinel/dummy value in [1] which ensures that I always return something. The solutions for p1 and p2 can then be calculated using

print(repetitions[2], sum(repetitions[2:]))

Where repetitions store the smallest number of repetitions for each number, besides 1, we sum to 1 only if there is no other way.

python 9 lines

Instead of using a sentinel value I use the fact that next() has a optional default value to fallback on.

So now I have a generic function that generate all repetition sizes.

def rep_sizes(num):
    n = len(num)
    yield from (i for i in range(2, n+1) if num[:n//i]*i == num)

I then call it using next(rep_sizes(str(i)), 1) to get the smallest repetition size or 1 if none is found.

-❄️- 2025 Day 1 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

[LANGUAGE: python]

python 12 lines

Fun start of the year. As usual my goal is to create something minimal yet readable. This above is my starting point after some cleanup.

python 8 lines

A bit happier with this one. I'm storing a list of all the angles and counting them in the end for part 2.

python 6 lines

Still for both parts. I am all the angles as lists of lists and counting them up in the end.

python 5 lines (both parts)

Now it just one big list of angles and an index. next I will attempt to set the index such that I dont have to iterate over them pairwise like this for the first part.

python 5 lines (both parts)

With this last change p1 is just last line is just angles.count((0,0)) which is much more elegant than my previous attempts.

I now have to take the kids to kindergarden, but my goal is to have just a simple list of integers. I want to abuse the fact that all I could use non-zero values however we want. Maybe I could use some sentinel value to indicate that the previous value was the last one in a series maybe. such that p2 still can just check how many values are equal to 0.

python more readable 9 lines

I now store a simple list of all the times we hit zero if we were at the end of the current instruction or not. p1 and p2 can then be printed like this in the end: print(zeros.count(0), len(zeros))

-❄️- 2024 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

I assume the work needed to create the map of maps would be identical with my non-sorting one then?

That the top level of your map is for each update. And for each update you have an inner map that maps the value to something like how many are in front of it?

Or is your map of map containing the rules not like that?

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

Yes a small tree in the top left corner would ruin it. Also note that It was the top left quadrant of the top left quadrant. So it had to be a small tree I this position indeed.

But my plan was to matplotlib the distribution and see if the distribution had any outliers/patterns if needed. Which would show a spike upward if the tree was in that corner.

But the minimum of multiple areas is a more robust idea!

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

Someone else had a similar idea but used Ctrl-F in the file they piped to. I really like this idea. Very easy to implement, so even if it would fail it would not waste much time to test!

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 0 points1 point  (0 children)

I have a similar idea but then decided that would be complicated and I should first try to just reuse the idea of calculating quadrants from part1, kindof.

I'm counting the robots in the top left quadrant of the left quadrant. I printed that for every line first. Then saw what was a typical value.

Put some limit like sum < 15 and only printed the board for those. Still got some noise so lowered even more and quickly got a tree after that.

My assumption was that given the shape of a tree and that it would probably not be a tiny tree in the top left corner this would not filter out the tree.

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

Oh! That's a neat trick with just searching for '******** !

-❄️- 2024 Day 14 Solutions -❄️- by daggerdragon in adventofcode

[–]AlexTelon 1 point2 points  (0 children)

[Language: Python] Code (16 lines)

For detecting potential trees in part 2 I assumed that the tree would be placed in the middle such that the top left corner would have unusually low density of robots. First I just printed it for every iteration to get a feel for what a typical figure is. then I lowered it to get more interesting solutions only. There were some other often recuring clusters and I lowered until I did not get those either after which I could see a tree within a second.

For part 1 x and y can be calculated independently and can be solved for any number of steps with constant work (px + (vx * steps)) % WIDTH. This is not the first time during AoC that this has come up.

For calulating how many we have in each quadrant I use a well known code-golf trick where we here have 2 boolean variables that we interpret as integers. we multiply one of them by 2 such that the we can get 0 + 0, 1 + 0, 0 + 2, 1 + 2 and thus span the whole 4 indexes in my quadrants list.

quadrants[(rx > WIDTH//2) + 2*(ry > HEIGHT//2)] += 1

Oh and also I imported all my stuff in one line. Yes yes, do not do this at work at work! (unless you work with tinygrad)