zerosleeps

Since 2010

Advent of Code 2024 day 3

My solution for day 3. The re module is one of my favourites in Python’s standard library.

import math
import re
import unittest


def part_one(raw_input):
    matches = re.findall(r"mul\((\d{1,3}),(\d{1,3})\)", raw_input)
    return sum([math.prod([int(value) for value in match]) for match in matches])


def do_or_dont(conditional_matches, location):
    enabled = True
    mapped_matches = {match.start(): match[1] for match in conditional_matches}
    for i in range(location):
        if i in mapped_matches:
            enabled = mapped_matches[i] == "do"
    return enabled


def part_two(raw_input):
    mul_matches = re.finditer(r"mul\((\d{1,3}),(\d{1,3})\)", raw_input)
    conditional_matches = list(re.finditer(r"(do|don't)\(\)", raw_input))
    return sum(
        [
            math.prod([int(value) for value in match.groups()])
            for match in mul_matches
            if do_or_dont(conditional_matches, match.start())
        ]
    )


class TestExamples(unittest.TestCase):
    def test_part_one(self):
        self.assertEqual(
            part_one(
                "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
            ),
            161,
        )

    def test_part_two(self):
        self.assertEqual(
            part_two(
                "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
            ),
            48,
        )


if __name__ == "__main__":
    with open("day_03_input.txt") as file:
        raw_input = file.read().strip()
    print(f"{part_one(raw_input)=}")
    print(f"{part_two(raw_input)=}")

Advent of Code 2024 day 2

After a false start because for some reason I thought each report had to be sorted first, I ended up with a solution to part one that I was reasonably happy with.

I dithered about with part two, but settled on just throwing all possible permutations back into my Report class. It’s not clever, but neither am I 🙂

import unittest


class Report:
    def __init__(self, reports):
        self.reports = reports

    @property
    def direction(self):
        if self.reports[1] > self.reports[0]:
            return "INCREASING"
        elif self.reports[1] < self.reports[0]:
            return "DECREASING"

    @property
    def safe(self):
        if not self.direction:
            return False
        for i, report in enumerate(self.reports[:-1]):
            if (
                self.direction == "INCREASING"
                and not 1 <= self.reports[i + 1] - report <= 3
            ):
                return False
            if (
                self.direction == "DECREASING"
                and not 1 <= report - self.reports[i + 1] <= 3
            ):
                return False
        return True


def part_one(reports):
    return len([report for report in reports if report.safe])


def part_two(reports):
    safe_reports_after_dampening = []
    for report in reports:
        if report.safe:
            safe_reports_after_dampening.append(report)
        else:
            for i in range(len(report.reports)):
                if Report(report.reports[:i] + report.reports[i + 1 :]).safe:
                    safe_reports_after_dampening.append(report)
                    break
    return len(safe_reports_after_dampening)


class TestExamples(unittest.TestCase):
    def setUp(self):
        self.input = [
            "7 6 4 2 1",
            "1 2 7 8 9",
            "9 7 6 2 1",
            "1 3 2 4 5",
            "8 6 4 4 1",
            "1 3 6 7 9",
        ]
        self.reports = [
            Report([int(report) for report in line.split()]) for line in self.input
        ]

    def test_part_one(self):
        self.assertEqual(part_one(self.reports), 2)

    def test_part_two(self):
        self.assertEqual(part_two(self.reports), 4)


if __name__ == "__main__":
    reports = [
        Report([int(report) for report in line.strip().split()])
        for line in open("day_02_input.txt")
    ]
    print(f"{part_one(reports)=}")
    print(f"{part_two(reports)=}")

Advent of Code 2024 day 1

Let’s see how far I get before tapping out this year. (I did a few puzzles in 2023 but didn’t post any of my solutions here.)

from collections import Counter
import unittest


def parse_raw_input(raw_input):
    return [
        sorted(list(column))
        for column in list(
            zip(*[[int(item) for item in line.strip().split()] for line in raw_input])
        )
    ]


def part_one(raw_input):
    return sum(abs(pair[0] - pair[1]) for pair in zip(*parse_raw_input(raw_input)))


def part_two(raw_input):
    left, right = parse_raw_input(raw_input)
    counter_for_right = Counter(right)
    return sum(i * counter_for_right[i] for i in left)


class TestExamples(unittest.TestCase):
    def setUp(self):
        self.raw_input = ["3   4", "4   3", "2   5", "1   3", "3   9", "3   3"]

    def test_part_one(self):
        self.assertEqual(part_one(self.raw_input), 11)

    def test_part_two(self):
        self.assertEqual(part_two(self.raw_input), 31)


if __name__ == "__main__":
    print(f"{part_one(open("day_01_input.txt").readlines())=}")
    print(f"{part_two(open("day_01_input.txt").readlines())=}")

Passwords have problems, but passkeys have more

David Heinemeier Hansson on hey.com.

Yeah I think he’s spot on with this. Passkeys solve a problem, but I’m not sure they solve the correct problem. The technology is bulletproof - we’ve been using public/private keys for decades - and when implemented properly is unquestionably more secure at the bits-and-bytes layer. But the problem with passwords is almost always human, and passkeys don’t really solve that.

As pointed out by John Gruber at Daring Fireball, passkeys only work if you use some kind of password manager. In my case that means I can only use passkeys when I’m using my own Mac or my own iPhone, which is a pretty big hurdle in some cases. And if you’re the kind of person who already uses a password manager then there’s a good chance that - like me - all your accounts already have long, unique, high-entropy passwords.

I suspect this explains why most services I’ve enabled passkeys for leave the traditional username/password login path enabled, meaning my accounts are still vulnerable to that kind of attack, so… what’s the point of the passkey?