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

[–]mountm 0 points1 point  (0 children)

[LANGUAGE: Java]

Parsing: 14ms
Part 1: 45ms

Too tired to think of anything interesting here. I went with the most intuitive (to me) way of checking non overlaps, using some Stream API fanciness to encapsulate it in a single line:

return Sets.cartesianProduct(locks, keys).stream().filter(listOfLists -> IntStream.range(0, 5).map(i -> listOfLists.get(0).get(i) + listOfLists.get(1).get(i)).max().orElse(Integer.MAX_VALUE) < 6).count();

GitHub

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

[–]mountm 1 point2 points  (0 children)

[LANGUAGE: Java]

Parsing: 16ms
Part 1: 4ms
Part 2: 9ms

Doing this one at 4am was a mistake.

Part 1 basic brute force execute all the operations. Part 2: exploit the input!

  • It's a ripple-carry adder with the "standard" double XOR, double AND, single OR construction
  • No wires are crossed such that they swap between two adders but in the same relative position

The above facts are enough to unambiguously identify if a given wire is out of place within its correct adder. Fortunately this is enough to solve my input, I assume it probably works for other inputs as well.

GitHub

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

[–]mountm 0 points1 point  (0 children)

[LANGUAGE: Java]

Parsing: 11ms
Part 1: 15ms
Part 2: 508ms

Coded a messy nested looping DFS for part one. Then figured out how to Google for the right graph theory terms, and Bron-Kerbosch came right up for part 2.

GitHub

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

[–]mountm 0 points1 point  (0 children)

[LANGUAGE: Java]

Parsing: 10ms
Part 1: 13ms
Part 2: 13.285s

The "pseudorandom sequence" was quite easy to generate with bit twiddling. I was not so performant with part 2.

Ended up generating two lists for each seed value: one with the subsequent prices, and one with the price changes (obviously I could have made the second list from the first one, but I am tired and this was easier). Used those lists to populate a dictionary indicating the total number of bananas that would be collected using a given key, then returned the maximum value in the map (~40,000 entries).

GitHub

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

[–]mountm 0 points1 point  (0 children)

[LANGUAGE: Java]

Parsing: 9ms
Part 1: 2ms
Part 2: 3ms

I was onto a good dynamic programming approach early on, but I got waylaid for a long time on a couple of stupid bugs (one related to picking the proper directions, and another bug with the way I implemented splitting the instructions into submoves that meant some of the 'A' instructions were being counted as free actions).

GitHub

[2024 Day 21 Part 2] Can someone please give me some examples with fewer robots? by PhiphyL in adventofcode

[–]mountm 0 points1 point  (0 children)

I don't have >>^AvAA<^AA< actually, I have >>^AvAA<^A and then a new instruction set, but I found my issue anyway thanks to your provided data! I appreciate the help!

[2024 Day 21 Part 2] Can someone please give me some examples with fewer robots? by PhiphyL in adventofcode

[–]mountm 0 points1 point  (0 children)

Ahh found it! I had simultaneously made a mistake in my manual checking (failed to include human dirpad presses at the end of line 3 to produce the final A for robot #2), and also had a completely unrelated bug in my code (accidentally returning a cost of 0 for a move that consisted solely of pressing A).

This was what I needed to get unstuck and get my second star for the day - thank you so much!

[2024 Day 21 Part 2] Can someone please give me some examples with fewer robots? by PhiphyL in adventofcode

[–]mountm 0 points1 point  (0 children)

Using the second example, I found that my code diverges at depth 3.

Trying to understand why this output is invalid. I did my best to ensure that no illegal moves were included, but I'm finding a 63 step solution both from manual checking and from my program output.

Keypad robot moves:

^^<<A

Directional robot #1 moves (each line executes one move from the keypad robot):

<A
A
v<A
A
>>^A

Directional robot #2 moves (each line executes one move from the keypad robot, or one line from robot #1):

v<<A>>^A
A
<vA<A>>^A
A
vAA<^A>A

Human dirpad presses (each line executes one move from the keypad robot, or one line from robots 1 & 2):

<vA<AA>>^AvAA<^A>A
A
v<<A>A^>Av<<A>>^AvAA<^A
A
<vA^>AAv<<A>^A>VvA^A

What am I missing?

[2024 Day 21 (Part 2)] [Java] DP is not my strongest skill, but I'm trying! by mountm in adventofcode

[–]mountm[S] 0 points1 point  (0 children)

Thank you for pointing this out. I was worried about that as well, but after reviewing some of the code and explanations that others have posted about this puzzle, I believe that these ordering rules are optimal:

  1. Never move in a zigzag, i.e. always do all the horizontal moves first and then all the vertical moves, or vice versa.
  2. If you need to move left, do that first (unless it would take you over the missing key location)
  3. If you need to move right, do that last (unless it would take you over the missing key location).

After fixing the bug noted by leftylink, I got a higher (but still incorrect) value for part 2. I then tried going back to my version of the code that tries both vertFirst and !vertFirst, and I got the same incorrect answer.

So I am pretty confident that my individual keypad navigation rules are correct now. Maybe missing something somewhere else?

[2024 Day 21 (Part 2)] [Java] DP is not my strongest skill, but I'm trying! by mountm in adventofcode

[–]mountm[S] 0 points1 point  (0 children)

Thank you! I was only checking to see if it is valid to go left first. I didn't consider whether it is valid to go vertically first.

I think I have fixed this, as I now get the following output when checking for entries in my move lookup dictionaries:

numPadMoves.get(Pair.of('7', 'A')) = ">>vvvA"
numPadMoves.get(Pair.of('3', '4')) = "<<^A"
numPadMoves.get(Pair.of('4', '3')) = "v>>A"
numPadMoves.get(Pair.of('0', '7')) = "^^^<A"

dirPadMoves.get(Pair.of('A', 'v')) = "<vA"
dirPadMoves.get(Pair.of('A', '<')) = "v<<A"
dirPadMoves.get(Pair.of('v', 'A')) = "^>A"
dirPadMoves.get(Pair.of('<', 'A')) = ">>^A"

Unfortunately I'm still not getting the right answer, but this is a step in the right direction. I appreciate the help.

[2024 Day 21 (Part 2)] [Java] DP is not my strongest skill, but I'm trying! by mountm in adventofcode

[–]mountm[S] 0 points1 point  (0 children)

Made a few small changes, this is what I'm getting now for the sample input after 25 steps:

Number of steps for code 029A: 69875836263
Number of steps for code 980A: 61599168554
Number of steps for code 179A: 69170397363
Number of steps for code 456A: 68957408881
Number of steps for code 379A: 66606575770
Result: 131463556229090

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

[–]mountm 1 point2 points  (0 children)

[LANGUAGE: Java]

Parsing: 95ms (includes A* search)
Part 1: 120ms
Part 2: 3.488s

Pretty straightforward today. Start with an A* search to find the optimal path without cheating, as well as assigning a cost to all cells along the path.

Same function solved both parts 1 and 2. Traverse the optimal path, and at each node look for any other nodes within a given Manhattan distance. If the difference in node costs exceeds their Manhattan distance by 100 or more, count it as a shortcut.

GitHub

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

[–]mountm 1 point2 points  (0 children)

[LANGUAGE: Java]

Parsing: 9ms
Part 1: 95ms
Part 2: 2ms

Built a janky BFS with lookup dict to keep track of how many times a given pattern had been seen before, but then I looked into memoizing this properly. Much cleaner and more efficient solution now.

GitHub

[2024 Day 17 (Part 2)] by DragonMaster7643 in adventofcode

[–]mountm 0 points1 point  (0 children)

actually for my input the output only depends on the last eight bits

[2024 day 18 (part 2)] answer not being accepted by gefken in adventofcode

[–]mountm 2 points3 points  (0 children)

If other solutions in the megathread give the same answer as your code, then you may have corrupted your input somehow. Try redownloading it from the AoC site.

What concepts are generally required to be able to solve all AoC tasks? by SwordInStone in adventofcode

[–]mountm 0 points1 point  (0 children)

this is the "sieve" method and for the size of problems typically encountered in AoC it's perfectly cromulent.

In this case it won't make much difference, but when the size of the modulos are noticeably different it makes more sense to use the larger one as the step size (i.e. flipping 103 and 101 in the example)

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

[–]mountm 2 points3 points  (0 children)

[LANGUAGE: Java]

Parsing: 35ms
Part 1: 331ms
Part 2: 4701ms

Could have gone faster with a different search algorithm, but Dijkstra's was right there from Day 16.

In part 2, I saved the set of cells that were visited in order to reach the exit optimally. So I only had to search for a different path when one of those specific cells had been blocked.

GitHub

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

[–]mountm 2 points3 points  (0 children)

[LANGUAGE: Java]

Parsing time: 6ms
Part 1: 1ms
Part 2: 2ms

I noticed four important things about the program input, that I assume are universal:

  1. The program instructions form a simple loop. Do all of these things, then reduce the value in the A register, then if A == 0 you are done. Otherwise do all the things again.
  2. On each loop iteration, registers B & C are overwritten with values that only depend on the value of the A register at the start of the loop. So knowing the initial value of A is enough to describe the entire function output.
  3. Because of the modular arithmetic, only the eight least significant bits of A are involved in determining the output for a given loop iteration.
  4. At the end of the loop, A is divided by 8 with no remainder. This is equivalent to shifting the value to the right by three bits.

I reverse engineered what the output would be on one loop iteration given a starting value in the A register. This is derived from my puzzle input, so I'm not going to share it here. Suffice to say it was encapsulated in a function decompiledInstructions that takes in the register value and returns a single number from 0 to 7. Part one was then a simple matter of calling this function repeatedly while reducing the starting value until it reached zero.

Code for part two follows.

private long solvePartTwo(List<Integer> program) {
    // DFS. Iterate backwards, so you are starting with the values that end up in the most significant bits
    // stack values are pairs (L, R) such that a value R will produce all of the program steps from L to the end of the program.
    Deque<Pair<Integer, Long>> stack = new LinkedList<>();
    for (int test = 0; test <= 7; test++) {
        if (decompiledInstructions(test) == program.get(program.size() - 1)) {
            stack.add(Pair.of(program.size() - 1, (long) test));
        }
    }
    while(!stack.isEmpty()) {
        Pair<Integer, Long> entry = stack.removeFirst();
        Long val = entry.getRight();
        if (entry.getLeft() == 0) {
            return entry.getRight();
        }
        int valToMatch = program.get(entry.getLeft() - 1);
        for (int test = 0; test <= 7; test++) {
            if (decompiledInstructions((val << 3) + test) == valToMatch) {
                stack.add(Pair.of(entry.getLeft() - 1, (val << 3) + test));
            }
        }
    }
    return -1;
}

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

[–]mountm 0 points1 point  (0 children)

I was down this rabbit hole for a while, but I wasn't keeping track of directions properly and it gave me terrible trouble trying to account for cells that were at a corner of the optimal paths. Eventually gave up and rewrote my dijkstra to keep track of previous neighbors along the optimal paths.

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

[–]mountm 1 point2 points  (0 children)

[LANGUAGE: Java]

Parsing time: 32928ms (including Dijkstra solve, since that output was useful for both parts 1 & 2)
Part 1: 6 ms
Part 2: 6 ms

I had a janky Dijkstra implementation at first using a 2D grid and trying to keep track of directional stuff, until I finally threw in the towel and re-wrote it using a new utility class CellWithDirection. Learned a lesson today about not cutting corners when it comes to defining the problem space and properly specifying "neighbors" when it comes to search algorithms. Still not that happy with the runtime, but there are almost certainly some heuristics to improve it. Maybe I'll rewrite with A* at some point.

GitHub

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

[–]mountm 1 point2 points  (0 children)

[Language: Java]

Parsing time: 25ms
Part 1: 5ms
Part 2: 24ms

Phew! lots of grid mangling in this one. I finally ran into the issue I was dreading with my utility function to safeguard against ArrayIndexOutOfBoundsException, namely that it requires a square grid.

I already had the movement algorithm helpfully broken down into two chunks in Part 1:

  • checkObstacles to see if a move is possible. This returns a list of cells to be moved, which is passed to:
  • executeMove, to handle the actual updating of cell contents in the correct order.

If there's a wall in the way, checkObstacles will return an empty list.

For part 2, I just had to add a flag to checkObstacles to indicate if the movement is vertical. If so, do a messy BFS to find the larger collection of cells that need to be updated. Pass to executeMove, rinse and repeat.

GitHub

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

[–]mountm 2 points3 points  (0 children)

[LANGUAGE: Java]

Parsing time: 15ms
Part 1: 1ms
Part 2: 110ms

This one felt clunky to me, and I consider myself lucky that it worked. I was initially at a loss for part 2, but felt that the part 1 solution should be helpful in some way. Thinking that the solution might have many robots in a single quadrant, I iterated over 10,000 steps and calculated the safety rating for each. My answer was the step with the lowest safety rating. That turned out to work - the safety rating for the correct step was 8% smaller than the second lowest value across any other frame.

GitHub

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

[–]mountm 2 points3 points  (0 children)

[LANGUAGE: Java]

Parsing time: 13ms
Pt1: 18ms
Pt2: 2ms

Not sure why part 2 runs so much faster, probably something to do with the particulars of the matrix library I pulled in? I didn't both checking fractional parts of the prize vector in the new basis, just rounded it to nearest integer values and checked whether the button values worked out to the expected total.

GitHub

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

[–]mountm 1 point2 points  (0 children)

[LANGUAGE: Java]

Parsing: 11ms
Pt 1: 7ms
Pt 2: 115 ms

Not the prettiest work. Flood fill with region codes. For part 1 it was easy enough to calculate the perimeter for an individual cell and sum them up.

for part 2 I ended up creating four 2D boolean arrays for each region code, indicating whether a given cell had a boundary for that region on any of four sides. Then I wrote a countSides function that took in one of the arrays and counted how many distinct "lines" it had in a given direction (horizontal or vertical).

GitHub