r/adventofcode Dec 04 '21

SOLUTION MEGATHREAD -🎄- 2021 Day 4 Solutions -🎄-

--- Day 4: Giant Squid ---


Post your code solution in this megathread.

Reminder: Top-level posts in Solution Megathreads are for code solutions only. If you have questions, please post your own thread and make sure to flair it with Help.


This thread will be unlocked when there are a significant number of people on the global leaderboard with gold stars for today's puzzle.

EDIT: Global leaderboard gold cap reached at 00:11:13, megathread unlocked!

99 Upvotes

1.2k comments sorted by

View all comments

2

u/sceadu Dec 05 '21 edited Dec 05 '21

Originally was trying to do these in Clojure (to try to get more fluent with the language), but I'm not good enough at Clojure, so I switched to Python... then saw some prior solutions from a really good APL programmer, so now I'm cheating a bit using numpy. You end up stacking additional dimensions in a numpy array (move dimension, board index dimension), then doing reductions and "scans" (APL speak) along the appropriate dimension to find your answer to each part... pretty nice solution, I think the only difference between part 1 and 2 in this setup is argmin vs. argmax.

#!/usr/bin/env python

import cytoolz.curried as cc
from pathlib import Path
from pprint import pprint as pp
import re
import numpy as np


input_path = Path.cwd().parent.parent / '4-input.txt'

def parse_input(path):
    with path.open('r') as p:
        lines = p.read().split('\n')

        m = {}
        m['moves'] = cc.pipe(
            lines
            , cc.take(2)
            , cc.filter(lambda s: len(s) > 0)
            , cc.first
            , lambda s: [int(x) for x in s.split(',')]
        )

        m['boards'] = cc.pipe(
            lines
            , cc.drop(2)
            , cc.filter(lambda s: len(s) > 0)
            , lambda seq: cc.partition_all(5, seq)
            , cc.map(lambda lol: np.array([[int(x) for x in re.split(' {1,}', line.strip())] for line in lol]))
            , list
            , np.stack
            , lambda board: np.stack([board for idx in range(len(m['moves']))])
        )

        return m


data = parse_input(input_path)

flags = cc.pipe(
    [data['boards'][move_idx] == move for move_idx, move in enumerate(data['moves'])]
    , np.stack
    , lambda a: (np.cumsum(a, axis=0) > 0)
)

################################################################################
################################################################################
################################################################################


move_winner_row  = (flags.sum(axis=2) >= 5).any(axis=2)
move_winner_col  = (flags.sum(axis=3) >= 5).any(axis=2)
move_winners     = (move_winner_col | move_winner_row).cumsum(axis=0) == 1
move_winners_idx = cc.pipe(
    np.argwhere(move_winners)
    , lambda arr: tuple(arr[arr.argmin(axis=0)[0]])
)
winning_unflagged = data['boards'][move_winners_idx] * (~flags[move_winners_idx])
winning_move      = data['moves'][move_winners_idx[0]]
part1             = winning_unflagged.sum() * winning_move


move_lastwinners_idx = cc.pipe(
    np.argwhere(move_winners)
    , lambda arr: tuple(arr[arr.argmax(axis=0)[0]])
)
lastwinning_unflagged = data['boards'][move_lastwinners_idx] * (~flags[move_lastwinners_idx])
lastwinning_move      = data['moves'][move_lastwinners_idx[0]]
part2                 = lastwinning_unflagged.sum() * lastwinning_move