#!/usr/bin/env python3
"""
Day 4: Giant Squid

https://adventofcode.com/2021/day/4
"""
import re

BingoNumbers = list[int]
Board = list[tuple[int, ...]]
Data = tuple[BingoNumbers, list[Board]]

RE_LINE = re.compile(r"^ ?(\d+) {1,2}(\d+) {1,2}(\d+) {1,2}(\d+) {1,2}(\d+)$")


def parse_file(path: str) -> Data:
    with open(path) as f:
        raw = f.read().splitlines()
        bingo_numbers = list(map(int, raw[0].split(",")))
        boards = [raw[i : i + 5] for i in range(2, len(raw), 6)]
        boards = [
            [tuple(int(match) for match in RE_LINE.findall(line)[0]) for line in board]
            for board in boards
        ]
    return bingo_numbers, boards


def transpose(lst: list) -> list:
    return list(zip(*lst))


def flatten(lst: list) -> list:
    return [item for sublist in lst for item in sublist]


def check_board(winning_nums: BingoNumbers, board: Board) -> bool:
    for line in board + transpose(board):
        if all(num in winning_nums for num in line):
            return True
    return False


def find_bingo_winner(
    bingo_numbers: BingoNumbers, boards: list[Board]
) -> list[tuple[BingoNumbers, Board]]:
    winning_nums = []
    winning_boards = []
    for bingo_number in bingo_numbers:
        winning_nums.append(bingo_number)
        done_boards = list(map(lambda x: x[1], winning_boards))
        for board in [board for board in boards if board not in done_boards]:
            if check_board(winning_nums, board):
                winning_boards.append((list(winning_nums), board))
    return winning_boards


def part_1(bingo_numbers: BingoNumbers, boards: list[Board]):
    """
    To guarantee victory against the giant squid, figure out which
    board will win first. What will your final score be if you choose
    that board?
    """
    winning_boards = find_bingo_winner(bingo_numbers, boards)

    if not winning_boards:
        return -1

    winning_nums, board = winning_boards[0]
    unmarked_nums = sum(filter(lambda x: x not in winning_nums, flatten(board)))
    return unmarked_nums * winning_nums[-1]


def part_2(bingo_numbers: BingoNumbers, boards: list[Board]):
    """
    Figure out which board will win last. Once it wins, what would its
    final score be?
    """
    winning_boards = find_bingo_winner(bingo_numbers, boards)

    if not winning_boards:
        return -1

    loosing_nums, board = winning_boards[-1]
    unmarked_nums = sum(filter(lambda x: x not in loosing_nums, flatten(board)))
    return unmarked_nums * loosing_nums[-1]


def main():
    bingo_nums, boards = parse_file("inputs/example_04.txt")

    print("Part 1", part_1(bingo_nums, boards))
    print("Part 2", part_2(bingo_nums, boards))


if __name__ == "__main__":
    main()

#!/usr/bin/env python3
"""
Day 4: Giant Squid

https://adventofcode.com/2021/day/4
"""
import re

BingoNumbers = list[int]
Board = list[tuple[int, ...]]
Data = tuple[BingoNumbers, list[Board]]

RE_LINE = re.compile(r"^ ?(\d+) {1,2}(\d+) {1,2}(\d+) {1,2}(\d+) {1,2}(\d+)$")


def parse_file(path: str) -> Data:
    with open(path) as f:
        raw = f.read().splitlines()
        bingo_numbers = list(map(int, raw[0].split(",")))
        boards = [raw[i : i + 5] for i in range(2, len(raw), 6)]
        boards = [
            [tuple(int(match) for match in RE_LINE.findall(line)[0]) for line in board]
            for board in boards
        ]
    return bingo_numbers, boards


def transpose(lst: list) -> list:
    return list(zip(*lst))


def flatten(lst: list) -> list:
    return [item for sublist in lst for item in sublist]


def check_board(winning_nums: BingoNumbers, board: Board) -> bool:
    for line in board + transpose(board):
        if all(num in winning_nums for num in line):
            return True
    return False


def find_bingo_winner(
    bingo_numbers: BingoNumbers, boards: list[Board]
) -> list[tuple[BingoNumbers, Board]]:
    winning_nums = []
    winning_boards = []
    for bingo_number in bingo_numbers:
        winning_nums.append(bingo_number)
        done_boards = list(map(lambda x: x[1], winning_boards))
        for board in [board for board in boards if board not in done_boards]:
            if check_board(winning_nums, board):
                winning_boards.append((list(winning_nums), board))
    return winning_boards


def part_1(bingo_numbers: BingoNumbers, boards: list[Board]):
    """
    To guarantee victory against the giant squid, figure out which
    board will win first. What will your final score be if you choose
    that board?
    """
    winning_boards = find_bingo_winner(bingo_numbers, boards)

    if not winning_boards:
        return -1

    winning_nums, board = winning_boards[0]
    unmarked_nums = sum(filter(lambda x: x not in winning_nums, flatten(board)))
    return unmarked_nums * winning_nums[-1]


def part_2(bingo_numbers: BingoNumbers, boards: list[Board]):
    """
    Figure out which board will win last. Once it wins, what would its
    final score be?
    """
    winning_boards = find_bingo_winner(bingo_numbers, boards)

    if not winning_boards:
        return -1

    loosing_nums, board = winning_boards[-1]
    unmarked_nums = sum(filter(lambda x: x not in loosing_nums, flatten(board)))
    return unmarked_nums * loosing_nums[-1]


def main():
    bingo_nums, boards = parse_file("inputs/example_04.txt")

    print("Part 1", part_1(bingo_nums, boards))
    print("Part 2", part_2(bingo_nums, boards))


if __name__ == "__main__":
    main()

#!/usr/bin/env python3 Day 4: Giant Squid  https://adventofcode.com/2021/day/4 To guarantee victory against the giant squid, figure out which     board will win first. What will your final score be if you choose     that board? Figure out which board will win last. Once it wins, what would its     final score be?