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 8e5e165..1faeac0 100644 --- a/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb +++ b/src/AoC_2023/Dazbo's_Advent_of_Code_2023.ipynb @@ -9231,7 +9231,13 @@ "\n", "(I'm already scared about Part 2.)\n", "\n", - "**Considering only the X and Y axes, check all pairs of hailstones' future paths for intersections. How many of these intersections occur within the test area?**\n" + "**Considering only the X and Y axes, check all pairs of hailstones' future paths for intersections. How many of these intersections occur within the test area?**\n", + "\n", + "**My solution:**\n", + "\n", + "This feels like a `y = mx + c` problem.\n", + "\n", + "For each line, we can obtain two `(x,y)` points, i.e. `(x,y)` at `t=0`, and `(x,y)`at `t=1`." ] }, { @@ -9242,10 +9248,61 @@ "source": [ "@dataclass\n", "class Hailstone():\n", - " posn: tuple[int, ...]\n", + " \"\"\" Represent the position of the stone at t=0, and the velocity \"\"\"\n", + " posn: tuple[float, ...]\n", " velocity: tuple[int, ...]\n", " \n", - "def parse_stones(data):\n", + " def posn_at_t(self, t: int) -> tuple[float, ...]:\n", + " \"\"\" Return the position at time t \"\"\"\n", + " new_axis_vals = []\n", + " for axis, axix_val in enumerate(self.posn):\n", + " new_axis_vals.append(axix_val + t*self.velocity[axis])\n", + " \n", + " return tuple(new_axis_vals)\n", + " \n", + " @property\n", + " def gradient(self) -> float:\n", + " \"\"\" \n", + " Determine the gradient as given by (y velocity) / (x velocity)\n", + " Raises ValueError if the x velocity is 0, then the gradient is infinite\n", + " \"\"\"\n", + " if self.velocity[0] == 0:\n", + " raise ValueError(\"Infinite gradient\")\n", + " \n", + " return self.velocity[1] / self.velocity[0]\n", + " \n", + " @property\n", + " def y_intercept(self) -> float:\n", + " \"\"\" \n", + " Determine the c value of y = mx + c \n", + " c = y - mx \n", + " \"\"\"\n", + " return self.posn[1] - self.gradient*self.posn[0]\n", + " \n", + " def intersects_with(self, other: Hailstone) -> tuple[float, ...]:\n", + " \"\"\" \n", + " For y = mx + c\n", + " x = (c2-c1) / (m1-m2)\n", + " \"\"\"\n", + " if self.gradient == other.gradient:\n", + " raise ValueError(\"Parallel paths\")\n", + " \n", + " x = (other.y_intercept - self.y_intercept) / (self.gradient - other.gradient)\n", + " y = self.gradient*x + self.y_intercept\n", + " \n", + " return (x,y)\n", + " \n", + " def time_at_posn(self, posn: tuple[float, ...]) -> float:\n", + " \"\"\" \n", + " Determine the time that this stone was at this position.\n", + " t = (xi - x0) / vx\n", + " where xi is the x coord at a given location\n", + " x0 is the original x coord\n", + " vx is the velocity in the x direction\n", + " \"\"\"\n", + " return (posn[0] - self.posn[0]) / self.velocity[0]\n", + " \n", + "def parse_stones(data) -> list[Hailstone]:\n", " stones = []\n", " for row in data:\n", " posn, velocity = row.split(\"@\")\n", @@ -9261,12 +9318,28 @@ "metadata": {}, "outputs": [], "source": [ - "def solve_part1(data: list[str], coord_min: int, coord_max: int):\n", + "def solve_part1(data: list[str], coord_min: int, coord_max: int) -> int:\n", + " \"\"\" \n", + " Detect any intersection of paths within the specified x,y area\n", + " and where the time that this hailstone occupies this location is not in the past. \n", + " \"\"\"\n", " stones = parse_stones(data)\n", - " logger.debug(stones)\n", - "\n", - "\n", - " " + " \n", + " intersections = 0\n", + " for stone_a, stone_b in combinations(stones, 2):\n", + " try:\n", + " intersect = stone_a.intersects_with(stone_b)\n", + " t_a = stone_a.time_at_posn(intersect)\n", + " t_b = stone_b.time_at_posn(intersect)\n", + " if (coord_min <= intersect[0] <= coord_max \n", + " and coord_min <= intersect[1] <= coord_max\n", + " and t_a >= 0 and t_b >= 0):\n", + " intersections += 1\n", + " # logger.debug(f\"{intersect} at {t_a=}, {t_b=}\")\n", + " except ValueError as e:\n", + " logger.debug(e) # e.g. parallel lines\n", + " \n", + " return intersections\n" ] }, { @@ -9290,7 +9363,7 @@ "\n", "logger.info(\"Tests passed!\")\n", "\n", - "soln = solve_part1(input_data)\n", + "soln = solve_part1(input_data, 200000000000000, 400000000000000)\n", "logger.info(f\"Part 1 soln={soln}\")" ] }, @@ -9300,7 +9373,10 @@ "source": [ "### Day 24 Part 2\n", "\n", - "Overview..." + "We can throw a rock that can reach any integer location and with any integer velocity. We want the rock to collide with EVERY hailstone. The rock itself won't change position or velocity when it hits a hailstone.\n", + "\n", + "**Determine the exact position and velocity the rock needs to have at time 0 so that it perfectly collides with every hailstone. What do you get if you add up the X, Y, and Z coordinates of that initial position?**\n", + "\n" ] }, { @@ -9320,8 +9396,7 @@ "outputs": [], "source": [ "%%time\n", - "sample_inputs = [\"abcdef\"]\n", - "sample_answers = [\"uvwxyz\"]\n", + "sample_answers = [47]\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",