zerosleeps

Since 2010

Site rebuild update

A little update on my rebuild of zerosleeps.com:

  • Post migration was sorted a while ago. Python-Markdown is super easy to work with
  • Code blocks are handled by the migration as well. Pygments is also a delight to work with.
  • My migration script also takes care of Jekyll image tags, converting them to plain Markdown image tags.
    • The image files themselves are going to be trickier because the current site uses a mishmash of image sizes and thumbnails. I’ve got things working for the happy-path, and will probably just have to manually update the markdown for anything else.
    • Not sure how I’m going to model post images in Django either. Should I bother with a model for them? I suppose so, or I won’t be able to leverage Django’s tools for uploading files. But do they belong to a post, or are they their own thing?
  • Post URLs… I said I wanted to change the structure of post URLs, but that I also want to keep the existing structure for compatibility. Well if I’m going to do the latter why bother with the former?
    • I didn’t think about post slugs though, which are just URL-safe copies of the post title. I have toyed with creating a custom Transform in Django to avoid storing post title and slug, but safer (and more flexible) to just add a slug field to my Post model.
  • “Static” pages, i.e. CV and About took 30 seconds each - they’re both just using TemplateView.

Oh and I’m using Bulma for the whole lot - I am not a designer!

Once I get post URLs sorted I reckon I’ll “go live” as they say in the business. If I don’t then I’ll never deploy the thing. Everything else can follow later: search, tags and categories (maybe?), visitor statistics, JSON and XML feeds, maybe a sitemap. There’s more to a stupid blog than you’d think.

Advent of Code 2022: I think I’m done

I think I’m done with Advent of Code for the year. I enjoyed the first couple of weeks: the problems were all distinct from each other and had well defined solutions so - for my taste/abilities - were largely about crafting pleasant code that is totally different to the kind of stuff I work on 09:00–17:00.

I could solve these puzzles in less than an hour, too. Perfect little brain-tickers.

But days 15 (for which I have a solution that works for the example but not my actual input), 16, and 17 are all way too computer science/mathematics for me. Pattern matching and algorithms and path-finding - they’re just not my thing.

And that’s okay! Of course I could teach myself the tools and tricks Advent of Code is so great at hinting at, I just don’t want to! I’ve got enough life experience and introspection to know what I enjoy and what I don’t enjoy. This is something I chose to do in my spare time, it stopped making me happy, and it wasn’t helping me to improve the personal traits I care about.

Let it be said, whatever your reasons for partaking, Advent of Code is magnificently well done. The guy behind it is a legend.

Now… I should probably set aside an hour each day for “Advent of fixing my damned website” 😬

Advent of Code 2022 day 14

Completed day 14 today as well. It’s hideously inefficient but the code fits my brain, so adapting it for part 2 was nice and quick and worked first time 🥳

from itertools import pairwise
from unittest import TestCase
from utils import get_raw_input


class Cave:
    def __init__(self, raw_input):
        self.build(raw_input)
        self.sand = set()
        self.overflowing = False

    def plot_rocks(self, x1, y1, x2, y2):
        if y1 == y2:
            return [(i, y1) for i in range(min([x1, x2]), max([x1, x2]) + 1)]
        else:
            return [(x1, i) for i in range(min([y1, y2]), max([y1, y2]) + 1)]

    def build(self, raw_input):
        self.rocks = set()
        for line in raw_input:
            for pair in pairwise(line.split(" -> ")):
                self.rocks.update(
                    self.plot_rocks(
                        int(pair[0].split(",")[0]),
                        int(pair[0].split(",")[1]),
                        int(pair[1].split(",")[0]),
                        int(pair[1].split(",")[1]),
                    )
                )

    @property
    def boundaries(self):
        # y_min is highest rock, y_max is lowest rock
        return {
            "x_min": min([rock[0] for rock in self.rocks]),
            "x_max": max([rock[0] for rock in self.rocks]),
            "y_min": min([rock[1] for rock in self.rocks]),
            "y_max": max([rock[1] for rock in self.rocks]),
        }

    def drop_sand(self, floor=False):
        sand_position = (500, 0)
        while True:
            if (
                not floor
                and (
                    sand_position[1] > self.boundaries["y_max"]
                    or sand_position[0] < self.boundaries["x_min"]
                    or sand_position[0] > self.boundaries["x_max"]
                )
            ) or (floor and (500, 0) in self.sand):
                # Out of bounds
                self.overflowing = True
                return
            elif (
                (sand_position[0], sand_position[1] + 1) not in self.rocks
                and (sand_position[0], sand_position[1] + 1) not in self.sand
                and (
                    not floor
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall straight down
                sand_position = (sand_position[0], sand_position[1] + 1)
            elif (
                (sand_position[0] - 1, sand_position[1] + 1) not in self.rocks
                and (sand_position[0] - 1, sand_position[1] + 1) not in self.sand
                and (
                    (not floor)
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall down-and-left
                sand_position = (sand_position[0] - 1, sand_position[1] + 1)
            elif (
                (sand_position[0] + 1, sand_position[1] + 1) not in self.rocks
                and (sand_position[0] + 1, sand_position[1] + 1) not in self.sand
                and (
                    (not floor)
                    or (floor and sand_position[1] + 1 < self.boundaries["y_max"] + 2)
                )
            ):
                # Can fall down-and-right
                sand_position = (sand_position[0] + 1, sand_position[1] + 1)
            else:
                # Can't move
                self.sand.add(sand_position)
                return


class TestExamples(TestCase):
    def setUp(self):
        self.example_input = [
            "498,4 -> 498,6 -> 496,6",
            "503,4 -> 502,4 -> 502,9 -> 494,9",
        ]
        self.cave = Cave(self.example_input)

    def test_part_one(self):
        self.assertEqual(run(self.cave), 24)

    def test_part_two(self):
        self.assertEqual(run(self.cave, True), 93)


def run(cave, floor=False):
    while not cave.overflowing:
        cave.drop_sand(floor)
    return len(cave.sand)


if __name__ == "__main__":
    print(f"Part one: {run(Cave(get_raw_input('day_14_input.txt')))}")
    print(f"Part two: {run(Cave(get_raw_input('day_14_input.txt')), True)}")

Advent of Code 2022 day 13 is done

I figured out my problem: I wasn’t correctly handing the scenario where a list had been exhausted with no result. I was incorrectly assuming that when either side of the list had been exhausted, a result must have been obtained.

Final code:

from functools import cmp_to_key
from utils import get_raw_input_as_str

def parse_raw_input(raw_input):
    return [[eval(packet) for packet in pair.splitlines()] for pair in raw_input.split("\n\n")]

def compare(left, right):
    if type(left) == type(right) == list:
        result = None
        i = 0
        while result is None:
            if len(left) != len(right) and len(left) <= i:
                return 1
            elif len(left) != len(right) and len(right) <= i:
                return -1
            elif len(left) == i and len(right) == i:
                return None
            else:
                result = compare(left[i], right[i])
                i += 1
        return result
    elif type(left) == type(right) == int:
        if left < right:
            return 1
        elif left > right:
            return -1
        else:
            return None
    elif type(left) == int and type(right) != int:
        return compare([left], right)
    elif type(right) == int and type(left) != int:
        return compare(left, [right])
    else:
        raise Exception

def part_one(input):
    return sum([i + 1 for i, pair in enumerate(input) if compare(pair[0], pair[1]) == 1])

def part_two(input):
    flattened_input = [packet for pair in input for packet in pair] + [[[2]]] + [[[6]]]
    sorted_packets = sorted(flattened_input, key=cmp_to_key(compare), reverse=True)
    return (sorted_packets.index([[2]]) + 1) * (sorted_packets.index([[6]]) + 1)

if __name__ == '__main__':
    print(f"Part one: {part_one(parse_raw_input(get_raw_input_as_str('day_13_input.txt')))}")
    print(f"Part one: {part_two(parse_raw_input(get_raw_input_as_str('day_13_input.txt')))}")