Close

Advent of Code 2025- Day 8

Playground

This one felt tricky at first with lots of words and seemingly numerous combinations of how to arrange the boxes, but the solutions turned out to be fairly straightforward. It wasn’t necessary to recognize any particular trick to solving them; but I will acknowledge that using languages that have native set-based syntax likely made it easier for me to implement than if I had to write MEMBER OF, MULTISET INTERSECT, or MULTISET UNION functionality from scratch.

When measuring the distance between two junction boxes, for the purposes of sorting closest to furthest it’s not necessary to calculate the square root of the sum of the squared differences on each axis; simply using the sum of the squares is sufficient for ordering. However, the performance boost by skipping that step is negligible for either part 1 or 2.

So, when calculating the distance “d” between two points, it didn’t really matter which of these I used.

sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z))

or

(a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)

In the code examples below I do fully calculate the distance with the square root, but if you’re looking for the absolute fastest method you could skip that step.

For Part 1, I join every junction box to all others. Since making a connection from A to B is the same as making the connection from B to A, I restrict the join to prevent these redundancies using “FROM data a JOIN data b ON b.seq > a.seq” thus ensuring each junction in the list is joined to one after it in the list.

For example, if there were just 3 junction boxes: 1, 2, and 3. I would join 1 to 2, 1 to 3, and 2 to 3; which would then be an exhaustive set. It would be redundant to also connect 2 to 1, 3 to 1 and 3 to 2.

Along with the join of boxes, I include the distance and then sort the pairs by those distances from closest together to furthest apart.

I use a slight difference in my list of circuits from that described in the puzzle text. It starts with circuits of 1 box each and then connects them. I only keep track of circuits of 2 or more boxes. For the purposes of solving the puzzle this difference is irrelevant in functionality, but made for more convenient coding.

As I iterate through each pair of boxes, I search through all circuits I have already built (which will be none on the first pass) looking of either or both boxes is a member of one of these circuits. If just one of the pair is part of a circuit then I add the other member to that circuit. After adding that new member, I then look through all remaining circuits until I find one that also contains that member. If I find one then I merge all members of that circuit into the first and shrink my list of circuits by one. If both members of the pair are already part of one circuit I do nothing. If I loop through all circuits and don’t find either member of the pair in any of them, then I start a new circuit of just that pair.

I repeat this process until I have processed 1000 pairs. Then I walk through my circuits keeping track of the 3 largest ones I find. I make the assumption there will always be at least 3 circuits built using the method above. If that assumption was not correct, I would dummy in 1 for each missing circuit to represent circuits of 1 box each, which, as I mentioned above, I’m not keeping track of explicitly.

In both the sample and my real data there were more than 3 circuits so I didn’t need to worry about that special case. Once I found the 3 largest, I multiply them together and get the result.

DECLARE
    TYPE circuit_tab IS TABLE OF numtab;

    v_circuits         circuit_tab := circuit_tab();
    v_connected        BOOLEAN;
    v_count1           PLS_INTEGER := 0;
    v_count2           PLS_INTEGER := 0;
    v_count3           PLS_INTEGER := 0;

    c_limit   CONSTANT PLS_INTEGER := 1000;
    v_connections      PLS_INTEGER := 0;
BEGIN
    FOR i
        IN (
               WITH data
                   AS
                       (SELECT seq,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 1), '99999') x,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 2), '99999') y,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 3), '99999') z
                          FROM advent.data2rows('advent2025-8'))
               SELECT *
                 FROM (  SELECT a.seq aseq,
                                b.seq bseq,
                                SQRT((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)) d
                           FROM data a JOIN data b ON b.seq > a.seq
                       ORDER BY d ASC)
           )
    LOOP
        v_connected := FALSE;

        FOR c IN 1 .. v_circuits.COUNT
                     WHEN NOT v_connected
        LOOP
            CASE
                WHEN i.aseq MEMBER OF v_circuits(c) AND i.bseq NOT MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;
                    v_connections := v_connections + 1;
                    v_circuits(c).EXTEND;
                    v_circuits(c)(v_circuits(c).COUNT) := i.bseq;

                    FOR j IN c + 1 .. v_circuits.COUNT
                    LOOP
                        IF v_circuits(j) MULTISET INTERSECT v_circuits(c) IS NOT EMPTY
                        THEN
                            v_circuits(c) := v_circuits(c) MULTISET UNION DISTINCT v_circuits(j);
                            v_circuits(j) := v_circuits(v_circuits.COUNT);
                            v_circuits.TRIM;
                            EXIT;
                        END IF;
                    END LOOP;
                WHEN i.bseq MEMBER OF v_circuits(c) AND i.aseq NOT MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;
                    v_connections := v_connections + 1;
                    v_circuits(c).EXTEND;
                    v_circuits(c)(v_circuits(c).COUNT) := i.aseq;

                    FOR j IN c + 1 .. v_circuits.COUNT
                    LOOP
                        IF v_circuits(j) MULTISET INTERSECT v_circuits(c) IS NOT EMPTY
                        THEN
                            v_circuits(c) := v_circuits(c) MULTISET UNION DISTINCT v_circuits(j);
                            v_circuits(j) := v_circuits(v_circuits.COUNT);
                            v_circuits.TRIM;
                            EXIT;
                        END IF;
                    END LOOP;
                WHEN i.aseq MEMBER OF v_circuits(c) AND i.bseq MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;
                    v_connections := v_connections + 1;
                ELSE
                    NULL;
            END CASE;
        END LOOP;

        IF v_connections = c_limit
        THEN
            EXIT;
        END IF;

        IF NOT v_connected
        THEN
            v_connections := v_connections + 1;
            v_circuits.EXTEND;
            v_circuits(v_circuits.COUNT) := numtab(i.aseq, i.bseq);
        END IF;

        IF v_connections = c_limit
        THEN
            EXIT;
        END IF;
    END LOOP;

    FOR c IN 1 .. v_circuits.COUNT
    LOOP
        CASE
            WHEN v_circuits(c).COUNT >= v_count1
            THEN
                v_count3 := v_count2;
                v_count2 := v_count1;
                v_count1 := v_circuits(c).COUNT;
            WHEN v_circuits(c).COUNT >= v_count2
            THEN
                v_count3 := v_count2;
                v_count2 := v_circuits(c).COUNT;
            WHEN v_circuits(c).COUNT >= v_count3
            THEN
                v_count3 := v_circuits(c).COUNT;
            ELSE
                NULL;
        END CASE;
    END LOOP;

    DBMS_OUTPUT.put_line(
           'Circuits:'
        || v_circuits.COUNT
        || ' Largest: '
        || v_count1
        || ' '
        || v_count2
        || ' '
        || v_count3
        || ' product: '
        || (v_count1 * v_count2 * v_count3));
END;

For Part 2, I use most of the same framework but I don’t need to keep track of how many pairs I go through. Instead, I first count how many boxes there are and then I start building my circuits as before except I don’t stop at fixed number of pairs, instead I keep building until all of my circuits merge into one that contains all of the junction boxes. At that point, there is no need to continue searching as every subsequent pair will be found to already be part of that one circuit and thus add nothing. As I cycle through each pair of boxes, I keep track of the x coordinates and when I add the last pair (even if one part of the pair was already in the circuit) I multiply those two x-values to get the result.

DECLARE
    c_file        VARCHAR2(20) := 'advent2025-8';

    TYPE circuit_tab IS TABLE OF numtab;

    v_circuits    circuit_tab := circuit_tab();
    v_connected   BOOLEAN;

    v_x1          PLS_INTEGER := 0;
    v_x2          PLS_INTEGER := 0;

    v_junctions   PLS_INTEGER;
BEGIN
    SELECT COUNT(*) INTO v_junctions FROM advent.data2rows(c_file);

    FOR i
        IN (
               WITH data
                   AS
                       (SELECT seq,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 1), '99999') x,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 2), '99999') y,
                               TO_NUMBER(REGEXP_SUBSTR(str, '[0-9]+', 1, 3), '99999') z
                          FROM advent.data2rows(c_file))
               SELECT xx.*, ROWNUM rn
                 FROM (  SELECT a.seq aseq,
                                b.seq bseq,
                                a.x xa,
                                b.x xb,
                                SQRT((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)) d
                           FROM data a JOIN data b ON b.seq > a.seq
                       ORDER BY d ASC) xx
           )
    LOOP
        v_connected := FALSE;

        FOR c IN 1 .. v_circuits.COUNT
                     WHEN NOT v_connected
        LOOP
            CASE
                WHEN i.aseq MEMBER OF v_circuits(c) AND i.bseq NOT MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;

                    v_circuits(c).EXTEND;
                    v_circuits(c)(v_circuits(c).COUNT) := i.bseq;
                    v_x1 := i.xa;
                    v_x2 := i.xb;

                    FOR j IN c + 1 .. v_circuits.COUNT
                    LOOP
                        IF v_circuits(j) MULTISET INTERSECT v_circuits(c) IS NOT EMPTY
                        THEN
                            v_circuits(c) := v_circuits(c) MULTISET UNION DISTINCT v_circuits(j);
                            v_circuits(j) := v_circuits(v_circuits.COUNT);
                            v_circuits.TRIM;
                            EXIT;
                        END IF;
                    END LOOP;
                WHEN i.bseq MEMBER OF v_circuits(c) AND i.aseq NOT MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;

                    v_circuits(c).EXTEND;
                    v_circuits(c)(v_circuits(c).COUNT) := i.aseq;
                    v_x1 := i.xa;
                    v_x2 := i.xb;

                    FOR j IN c + 1 .. v_circuits.COUNT
                    LOOP
                        IF v_circuits(j) MULTISET INTERSECT v_circuits(c) IS NOT EMPTY
                        THEN
                            v_circuits(c) := v_circuits(c) MULTISET UNION DISTINCT v_circuits(j);
                            v_circuits(j) := v_circuits(v_circuits.COUNT);
                            v_circuits.TRIM;
                            EXIT;
                        END IF;
                    END LOOP;
                WHEN i.aseq MEMBER OF v_circuits(c) AND i.bseq MEMBER OF v_circuits(c)
                THEN
                    v_connected := TRUE;
                ELSE
                    NULL;
            END CASE;
        END LOOP;

        IF v_circuits.COUNT > 0 AND v_circuits(1).COUNT = v_junctions
        THEN
            EXIT;
        END IF;

        IF NOT v_connected
        THEN
            v_circuits.EXTEND;
            v_circuits(v_circuits.COUNT) := numtab(i.aseq, i.bseq);
            v_x1 := i.xa;
            v_x2 := i.xb;
        END IF;

        IF v_circuits(1).COUNT = v_junctions
        THEN
            EXIT;
        END IF;
    END LOOP;

    DBMS_OUTPUT.put_line('x1: ' || v_x1 || ' x2: ' || v_x2 || ' product: ' || (v_x1 * v_x2));
END;

This will likely be my last puzzle of the year as I take time off to spend with my family for the holidays.

Merry Christmas and Happy New Year!

2 thoughts on “Advent of Code 2025- Day 8

  1. Hi Sean,

    Thanks a lot for all these nice posts 🙂
    I really enjoy following step by step your logic and reading through all your code 🙂

    Wish you and your family a Great and Happy Holidays Season,
    and a New Year 2026 full of health, happiness and the joy of all the upcoming challenges 🙂

    Best Regards,
    Iudith Mentzel

    1. Thank you Iudith and I wish you and your family well too.

      I’m not sure how many more of the puzzles I’ll do, but I just did one this morning and will be writing up another article for it.

Leave a Reply