diff --git a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb index 7edfc08..a6a83ba 100644 --- a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb +++ b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb @@ -68,7 +68,7 @@ "source": [ "from __future__ import annotations\n", "from dataclasses import asdict, dataclass, field\n", - "from typing import Optional, cast\n", + "from typing import Optional, Callable, cast\n", "from enum import Enum, auto\n", "from functools import cache, reduce\n", "from itertools import permutations, combinations, count, cycle\n", @@ -8090,6 +8090,229 @@ "logger.info(f\"Part 2 soln={soln}\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Day 22: Sand Slabs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DAY = \"22\" # replace with actual number (without leading digit)\n", + "day_link = f\"#### See [Day {DAY}](https://adventofcode.com/{YEAR}/day/{DAY}).\"\n", + "display(Markdown(day_link))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "d_name = \"d\" + str(DAY).zfill(2) # e.g. d01\n", + "script_name = \"aoc\" + str(YEAR) + d_name # e.g. aoc2017d01\n", + "locations = get_locations(d_name)\n", + "\n", + "# SETUP LOGGING\n", + "logger.setLevel(logging.DEBUG)\n", + "# td.setup_file_logging(logger, locations.output_dir)\n", + "\n", + "# Retrieve input and store in local file\n", + "try:\n", + " write_puzzle_input_file(YEAR, DAY, locations)\n", + " with open(locations.input_file, mode=\"rt\") as f:\n", + " input_data = f.read().splitlines()\n", + "\n", + " logger.info(\"Input data:\\n%s\", top_and_tail(input_data))\n", + "except ValueError as e:\n", + " logger.error(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Day 22 Part 1\n", + "\n", + "We need to disintegrate bricks of sand into free flowing sand. The bricks are falling into a stack.\n", + "\n", + "We have a snaptshot of the bricks whilst they were falling. The example looks like this:\n", + "\n", + "```text\n", + "1,0,1~1,2,1\n", + "0,0,2~2,0,2\n", + "0,2,3~2,2,3\n", + "0,0,4~0,2,4\n", + "2,0,5~2,2,5\n", + "0,1,6~2,1,6\n", + "1,1,8~1,1,9\n", + "```\n", + "\n", + "- Each row represents two 3D coordinates, `x,y,z`. Each coordinate is a cube. Thus, each brick can be represented as a number of cubes.\n", + " - The z-axis extends up into their air, whereas the x and y axes represent a plane at any given value of z.\n", + " - 2,2,2~2,2,2: This is a single cube brick.\n", + " - 0,0,10~1,0,10: This is a two cube brick, oriented horizontally. I.e. the x axis has coordinates 0 and 1.\n", + " - 0,0,1~0,0,10: This is a 10-cube brick, oriented vertically. I.e. the y axis is from 1 to 10 inclusive.\n", + "- The ground is at `z=0`.\n", + "- The lowest possible z coordinate of a brick is 1.\n", + "- The input shows the order the bricks fell. I.e. FIFO with the first at the top. \n", + "- Some bricks are still falling through the air!! For example, there is a one unit z gap between the last two bricks.\n", + "- Bricks always fall with constant orientation. I.e. only their z value will change as they fall.\n", + "\n", + "Oh god. It's Jenga!\n", + "\n", + "**Figure how the blocks will settle based on the snapshot. Once they've settled, consider disintegrating a single brick; how many bricks could be safely chosen as the one to get disintegrated?**\n", + "\n", + "**My solution:**\n", + "\n", + "We need to:\n", + "\n", + "- Determine the configuration of the bricks once they've settled.\n", + "- Determine which bricks then support other bricks.\n", + "- We can disintegrate any bricks that are not the sole support of another brick.\n", + "\n", + "Approach:\n", + "\n", + "- We can settle bricks based on occupied coordinates. Looking at the real input, I can see that that bricks dimensions are always single digit, so we should be okay to store occupied locations in a set. I.e. the set volume space won't get too huge.\n", + "- Then, we can build a graph to represent the support network.\n", + "\n", + "First, a `Brick` class:\n", + "\n", + "- Stores the two corners.\n", + "- Can determine it's lower and upper values of any axis.\n", + "- Can update its z values (i.e. as it falls)\n", + "- Can return the inclusive range of any axis.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class Brick():\n", + " corner_1: tuple[int,int,int]\n", + " corner_2: tuple[int,int,int]\n", + " \n", + " def max_for_axis(self, axis: int):\n", + " return self._axis_limit(max, axis)\n", + " \n", + " def min_for_axis(self, axis: int):\n", + " return self._axis_limit(min, axis)\n", + " \n", + " def _axis_limit(self, op: Callable, axis: int):\n", + " return op(self.corner_1[axis], self.corner_2[axis])\n", + " \n", + " def update_z(self, new_bottom_z: int):\n", + " z_delta = self.min_for_axis(2) - new_bottom_z\n", + " self.corner_1 = (self.corner_1[0], self.corner_1[1], self.corner_1[2] - z_delta)\n", + " self.corner_2 = (self.corner_2[0], self.corner_2[1], self.corner_2[2] - z_delta)\n", + " \n", + " def get_dim_range(self, axis: int):\n", + " return (self.min_for_axis(axis), self.max_for_axis(axis))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def parse_bricks(data: list[str]) -> list[Brick]: \n", + " bricks = []\n", + " for line in data:\n", + " corner_1, corner_2 = line.split(\"~\")\n", + " (x1, y1, z1) = [int(val) for val in corner_1.split(\",\")]\n", + " (x2, y2, z2) = [int(val) for val in corner_2.split(\",\")]\n", + " bricks.append(Brick((x1, y1, z1), (x2, y2, z2)))\n", + " \n", + " return bricks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def solve_part1(data):\n", + " bricks = parse_bricks(data)\n", + " \n", + " for brick in bricks:\n", + " logger.debug(f\"{brick=}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "sample_inputs = []\n", + "sample_inputs.append(\"\"\"1,0,1~1,2,1\n", + "0,0,2~2,0,2\n", + "0,2,3~2,2,3\n", + "0,0,4~0,2,4\n", + "2,0,5~2,2,5\n", + "0,1,6~2,1,6\n", + "1,1,8~1,1,9\"\"\")\n", + "sample_answers = [5]\n", + "\n", + "for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n", + " validate(solve_part1(curr_input.splitlines()), curr_ans) # test with sample data\n", + "\n", + "logger.info(\"Tests passed!\")\n", + "\n", + "soln = solve_part1(input_data)\n", + "logger.info(f\"Part 1 soln={soln}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Day 22 Part 2\n", + "\n", + "Overview..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def solve_part2(data):\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "sample_inputs = [\"abcdef\"]\n", + "sample_answers = [\"uvwxyz\"]\n", + "\n", + "for curr_input, curr_ans in zip(sample_inputs, sample_answers):\n", + " validate(solve_part2(curr_input), curr_ans) # test with sample data\n", + "\n", + "logger.info(\"Tests passed!\")\n", + "\n", + "soln = solve_part2(input_data)\n", + "logger.info(f\"Part 2 soln={soln}\")" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/src/AoC_2023/d22/input/input.txt b/src/AoC_2023/d22/input/input.txt new file mode 100644 index 0000000..f417051 Binary files /dev/null and b/src/AoC_2023/d22/input/input.txt differ