From b09d5d16528558ca580540608d94b099c9befd91 Mon Sep 17 00:00:00 2001 From: swatts Date: Wed, 6 Dec 2023 15:52:53 +0100 Subject: [PATCH] aoc days 5+6 --- _freeze/aoc/aoc/execute-results/html.json | 4 +- aoc/aoc.qmd | 137 ++++++++++++++++++++++ aoc/create_quarto_doc.py | 2 + 3 files changed, 141 insertions(+), 2 deletions(-) diff --git a/_freeze/aoc/aoc/execute-results/html.json b/_freeze/aoc/aoc/execute-results/html.json index 381ca1f..11263e1 100644 --- a/_freeze/aoc/aoc/execute-results/html.json +++ b/_freeze/aoc/aoc/execute-results/html.json @@ -1,7 +1,7 @@ { - "hash": "09a49daa543b453fd6fb6a5841ae9a21", + "hash": "ba5dd6b0820ad4dc477ab91427e82221", "result": { - "markdown": "---\ntoc: true\ndescription: Advent of Code 2023 Solutions in Python\ncategories:\n - python\ntitle: \"\\U0001F384\"\ndate: '2023-12-01'\ncode-line-numbers: true\nhighlight-style: github\n---\n\n## 1\nQuite challenging for a day 1! Learned some new regex for part 2 which was fun - positive lookahead `?=...` essentially means you can extract overlapping matches\n\n::: {.column-page}\n\n::: {.cell execution_count=1}\n``` {.python .cell-code}\nwith open(\"aoc/1/input.txt\", \"r\") as f:\n inp = f.readlines()\n\nONE_TO_NINE = list(map(str, list(range(1, 10))))\n\n\ndef extract_first_num(a):\n for char in a:\n if char in ONE_TO_NINE:\n return char\n\n\ntotal = 0\nfor row in inp:\n total += int(extract_first_num(row) + extract_first_num(row[::-1]))\n\nprint(\"Part 1 answer:\")\nprint(total)\n\nimport re\n\n\ndef convert_num(x):\n num_map = dict(\n zip(\n [\"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"seven\", \"eight\", \"nine\"],\n ONE_TO_NINE,\n )\n )\n\n if x.isnumeric():\n return x\n else:\n return num_map[x]\n\n\ntotal = 0\nfor row in inp:\n cap = re.findall(r\"(?=(\\d|one|two|three|four|five|six|seven|eight|nine))\", row)\n total += int(convert_num(cap[0]) + convert_num(cap[-1]))\n\n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n54951\nPart 2 answer:\n55218\n```\n:::\n:::\n\n\n:::\n## 2\nFeel like this should have been day one 😄\n\n::: {.column-page}\n\n::: {.cell execution_count=2}\n``` {.python .cell-code}\nwith open(\"aoc/2/input.txt\", \"r\") as f:\n inp = f.readlines()\n \ntotal = 0\n \nfor row in inp:\n index, game = row.split(\":\")\n index = int(index.replace(\"Game \", \"\"))\n possible = True\n \n for game in game.split(\";\"):\n bag_one = dict(\n red=12,\n green=13,\n blue=14,\n )\n \n for colours in game.split(\",\"):\n num, color = colours.strip().split(\" \")\n if int(num) > bag_one[color]:\n possible = False\n \n if possible:\n total += index\n \nprint(\"Part 1 answer:\")\nprint(total)\n\nimport math\n\ntotal = 0\n\nfor row in inp:\n index, game = row.split(\":\")\n index = int(index.replace(\"Game \", \"\"))\n bag_max = dict(\n red=0,\n green=0,\n blue=0,\n )\n for game in game.split(\";\"): \n for colours in game.split(\",\"):\n num, color = colours.strip().split(\" \")\n if int(num) > bag_max[color]:\n bag_max[color] = int(num)\n \n total += math.prod(bag_max.values())\n \nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n2563\nPart 2 answer:\n70768\n```\n:::\n:::\n\n\n:::\n## 3\nI don't like grids 🫠 I probably made this harder than it needed to be. If I were to do this again I probably would have just used euclidian distance comparisons\n\n::: {.column-page}\n\n::: {.cell execution_count=3}\n``` {.python .cell-code}\nimport re\nfrom collections import defaultdict\nfrom itertools import product\n\nwith open(\"aoc/3/input.txt\", \"r\") as f:\n inp = f.readlines()\n \n# examine all unique chars to populate regex pattern and SYMBOLS set\n# print(set(y for x in inp for y in x))\n \ntotal = 0\nSYMBOLS = {\"*\", \"#\", \"$\", \"+\", \"-\", \"%\", \"=\", \"/\", \"&\", \"@\"}\np = re.compile(\"\\d+|\\*|#|\\$|\\+|-|%|=|\\/|&|@\")\n\ngrid = []\nsyms = []\n\ndef poss_neighbours(i: int, j: tuple):\n i_s = list(filter(lambda x: x >= 0, [i, i-1, i+1]))\n j_s = list(filter(lambda x: x >=0 and x < len(inp[0]), [*j, j[0]-1, j[-1]+1]))\n \n for out_i in i_s:\n for out_j in j_s:\n if out_i == i and out_j in j:\n continue\n \n yield out_i, out_j\n\nassert set(poss_neighbours(0, (0,))) == {(1,0), (1,1), (0,1)}\n \n# construct the data structures for iterating over numbers and symbols\nfor i, row in enumerate(inp):\n for m in p.finditer(row):\n group = m.group()\n js = tuple(range(*m.span()))\n out = (group, i, js)\n \n if m.group() in SYMBOLS:\n syms.append(\n (group, i, js[0])\n )\n else:\n grid.append(\n (int(group), i, js)\n ) \n \n# part 1 logic\nfor num, i, js in grid:\n poss = list(poss_neighbours(i, js))\n \n for _, sym_i, sym_j in syms:\n if (sym_i, sym_j) in poss:\n total += num\n\nprint(\"Part 1 answer:\")\nprint(total)\n\nimport math\ntotal = 0\n\n# part 2 logic\nfor sym, i, j in syms:\n if sym == \"*\":\n poss = list(poss_neighbours(i, (j, )))\n \n adj = set()\n for num, num_i, num_js in grid:\n for num_j in num_js:\n if (num_i, num_j) in poss:\n adj.add(num)\n \n if len(adj) == 2:\n total += math.prod(adj)\n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n551094\nPart 2 answer:\n80179647\n```\n:::\n:::\n\n\n:::\n## 4\nEnjoyed the logic for the second part with the copies. I'm sure there was potential to go on a wild goose chase with recursion here, so I'm happy to have avoided the temptation 🤣\n\n::: {.column-page}\n\n::: {.cell execution_count=4}\n``` {.python .cell-code}\nwith open(\"aoc/4/input.txt\", \"r\") as f:\n inp = f.readlines()\n \ntotal = 0\n\n# part 1 logic\nfor row in inp:\n index, rest = row.split(\":\")\n \n win, play = rest.split(\"|\")\n win = list(filter(lambda x: x.isnumeric(), win.strip().split(\" \")))\n play = list(filter(lambda x: x.isnumeric(), play.strip().split(\" \")))\n score = 0\n \n \n for num in win:\n if num in play:\n score += 1\n \n if score > 0:\n total += 2 ** (score - 1)\n \n\nprint(\"Part 1 answer:\")\nprint(total)\n\ntotal = 0\ncopies = {}\n\n# part 2 logic\nfor row in inp:\n index, rest = row.split(\":\")\n index = int(index.split(\"d\")[1].strip())\n \n win, play = rest.split(\"|\")\n win = list(filter(lambda x: x.isnumeric(), win.strip().split(\" \")))\n play = list(filter(lambda x: x.isnumeric(), play.strip().split(\" \")))\n score = 0\n \n for num in win:\n if num in play:\n score += 1\n \n for x in range(index+1, index+score+1):\n copies[x] = copies.get(x, 0) + copies.get(index, 0) + 1\n \n total += copies.get(index, 0) + 1\n \n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n23678\nPart 2 answer:\n15455663\n```\n:::\n:::\n\n\n:::\n\n", + "markdown": "---\ntoc: true\ndescription: Advent of Code 2023 Solutions in Python\ncategories:\n - python\ntitle: \"\\U0001F384\"\ndate: '2023-12-01'\ncode-line-numbers: true\nhighlight-style: github\n---\n\n## 1\nQuite challenging for a day 1! Learned some new regex for part 2 which was fun - positive lookahead `?=...` essentially means you can extract overlapping matches\n\n::: {.column-page}\n\n::: {.cell execution_count=1}\n``` {.python .cell-code}\nwith open(\"aoc/1/input.txt\", \"r\") as f:\n inp = f.readlines()\n\nONE_TO_NINE = list(map(str, list(range(1, 10))))\n\n\ndef extract_first_num(a):\n for char in a:\n if char in ONE_TO_NINE:\n return char\n\n\ntotal = 0\nfor row in inp:\n total += int(extract_first_num(row) + extract_first_num(row[::-1]))\n\nprint(\"Part 1 answer:\")\nprint(total)\n\nimport re\n\n\ndef convert_num(x):\n num_map = dict(\n zip(\n [\"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"seven\", \"eight\", \"nine\"],\n ONE_TO_NINE,\n )\n )\n\n if x.isnumeric():\n return x\n else:\n return num_map[x]\n\n\ntotal = 0\nfor row in inp:\n cap = re.findall(r\"(?=(\\d|one|two|three|four|five|six|seven|eight|nine))\", row)\n total += int(convert_num(cap[0]) + convert_num(cap[-1]))\n\n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n54951\nPart 2 answer:\n55218\n```\n:::\n:::\n\n\n:::\n## 2\nFeel like this should have been day one 😄\n\n::: {.column-page}\n\n::: {.cell execution_count=2}\n``` {.python .cell-code}\nwith open(\"aoc/2/input.txt\", \"r\") as f:\n inp = f.readlines()\n \ntotal = 0\n \nfor row in inp:\n index, game = row.split(\":\")\n index = int(index.replace(\"Game \", \"\"))\n possible = True\n \n for game in game.split(\";\"):\n bag_one = dict(\n red=12,\n green=13,\n blue=14,\n )\n \n for colours in game.split(\",\"):\n num, color = colours.strip().split(\" \")\n if int(num) > bag_one[color]:\n possible = False\n \n if possible:\n total += index\n \nprint(\"Part 1 answer:\")\nprint(total)\n\nimport math\n\ntotal = 0\n\nfor row in inp:\n index, game = row.split(\":\")\n index = int(index.replace(\"Game \", \"\"))\n bag_max = dict(\n red=0,\n green=0,\n blue=0,\n )\n for game in game.split(\";\"): \n for colours in game.split(\",\"):\n num, color = colours.strip().split(\" \")\n if int(num) > bag_max[color]:\n bag_max[color] = int(num)\n \n total += math.prod(bag_max.values())\n \nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n2563\nPart 2 answer:\n70768\n```\n:::\n:::\n\n\n:::\n## 3\nI don't like grids 🫠 I probably made this harder than it needed to be. If I were to do this again I probably would have just used euclidian distance comparisons\n\n::: {.column-page}\n\n::: {.cell execution_count=3}\n``` {.python .cell-code}\nimport re\nfrom collections import defaultdict\nfrom itertools import product\n\nwith open(\"aoc/3/input.txt\", \"r\") as f:\n inp = f.readlines()\n \n# examine all unique chars to populate regex pattern and SYMBOLS set\n# print(set(y for x in inp for y in x))\n \ntotal = 0\nSYMBOLS = {\"*\", \"#\", \"$\", \"+\", \"-\", \"%\", \"=\", \"/\", \"&\", \"@\"}\np = re.compile(\"\\d+|\\*|#|\\$|\\+|-|%|=|\\/|&|@\")\n\ngrid = []\nsyms = []\n\ndef poss_neighbours(i: int, j: tuple):\n i_s = list(filter(lambda x: x >= 0, [i, i-1, i+1]))\n j_s = list(filter(lambda x: x >=0 and x < len(inp[0]), [*j, j[0]-1, j[-1]+1]))\n \n for out_i in i_s:\n for out_j in j_s:\n if out_i == i and out_j in j:\n continue\n \n yield out_i, out_j\n\nassert set(poss_neighbours(0, (0,))) == {(1,0), (1,1), (0,1)}\n \n# construct the data structures for iterating over numbers and symbols\nfor i, row in enumerate(inp):\n for m in p.finditer(row):\n group = m.group()\n js = tuple(range(*m.span()))\n out = (group, i, js)\n \n if m.group() in SYMBOLS:\n syms.append(\n (group, i, js[0])\n )\n else:\n grid.append(\n (int(group), i, js)\n ) \n \n# part 1 logic\nfor num, i, js in grid:\n poss = list(poss_neighbours(i, js))\n \n for _, sym_i, sym_j in syms:\n if (sym_i, sym_j) in poss:\n total += num\n\nprint(\"Part 1 answer:\")\nprint(total)\n\nimport math\ntotal = 0\n\n# part 2 logic\nfor sym, i, j in syms:\n if sym == \"*\":\n poss = list(poss_neighbours(i, (j, )))\n \n adj = set()\n for num, num_i, num_js in grid:\n for num_j in num_js:\n if (num_i, num_j) in poss:\n adj.add(num)\n \n if len(adj) == 2:\n total += math.prod(adj)\n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n551094\nPart 2 answer:\n80179647\n```\n:::\n:::\n\n\n:::\n## 4\nEnjoyed the logic for the second part with the copies. I'm sure there was potential to go on a wild goose chase with recursion here, so I'm happy to have avoided the temptation 🤣\n\n::: {.column-page}\n\n::: {.cell execution_count=4}\n``` {.python .cell-code}\nwith open(\"aoc/4/input.txt\", \"r\") as f:\n inp = f.readlines()\n \ntotal = 0\n\n# part 1 logic\nfor row in inp:\n index, rest = row.split(\":\")\n \n win, play = rest.split(\"|\")\n win = list(filter(lambda x: x.isnumeric(), win.strip().split(\" \")))\n play = list(filter(lambda x: x.isnumeric(), play.strip().split(\" \")))\n score = 0\n \n \n for num in win:\n if num in play:\n score += 1\n \n if score > 0:\n total += 2 ** (score - 1)\n \n\nprint(\"Part 1 answer:\")\nprint(total)\n\ntotal = 0\ncopies = {}\n\n# part 2 logic\nfor row in inp:\n index, rest = row.split(\":\")\n index = int(index.split(\"d\")[1].strip())\n \n win, play = rest.split(\"|\")\n win = list(filter(lambda x: x.isnumeric(), win.strip().split(\" \")))\n play = list(filter(lambda x: x.isnumeric(), play.strip().split(\" \")))\n score = 0\n \n for num in win:\n if num in play:\n score += 1\n \n for x in range(index+1, index+score+1):\n copies[x] = copies.get(x, 0) + copies.get(index, 0) + 1\n \n total += copies.get(index, 0) + 1\n \n\nprint(\"Part 2 answer:\")\nprint(total)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n23678\nPart 2 answer:\n15455663\n```\n:::\n:::\n\n\n:::\n## 5\nStuck on part 2 for now...\n\n::: {.column-page}\n\n::: {.cell execution_count=5}\n``` {.python .cell-code}\nfrom collections import OrderedDict\nfrom typing import Any\n\nwith open(\"aoc/5/input.txt\", \"r\") as f:\n inp = f.readlines()\n \n\n# create maps\nseeds = list(map(int, inp.pop(0).replace(\"seeds: \", \"\").strip().split(\" \")))\nmaps = OrderedDict()\n\nclass Mapper:\n def __init__(self, dest, source, rng):\n self.dest = dest\n self.source = source\n self.rng = rng\n \n def check(self, x):\n return self.source <= x < (self.source + self.rng)\n \n def __call__(self, x) -> Any:\n return x + (self.dest - self.source)\n \n def __repr__(self):\n return f\"{self.dest=}|{self.source=}|{self.rng=}\"\n\n\nfor line in inp:\n if line == \"\\n\":\n continue\n \n if \"map\" in line:\n map_name = line.replace(\"\\n\", \"\").replace(\" map:\", \"\")\n \n else:\n dest, source, rng = map(int, line.replace(\"\\n\", \"\").split(\" \"))\n \n maps.setdefault(map_name, []).append(\n Mapper(dest, source, rng)\n )\n \nlocations = []\n \nfor x in seeds:\n print(\"seed:\", x)\n for k in maps.keys():\n current_map = maps[k]\n \n for f in current_map:\n if f.check(x):\n x = f(x)\n break\n \n locations.append(x)\n \nprint(\"Part 1 answer:\")\nprint(min(locations))\n\n\n# This is incredibly slow as too many ints to check!\n# Need to try another approach using ranges!\n# locations = []\n \n# for z, y in zip(seeds[::2], seeds[1::2]):\n# for x in range(z, z+y):\n# # print(\"seed:\", x)\n# for k in maps.keys():\n# current_map = maps[k]\n \n# for f in current_map:\n# if f.check(x):\n# x = f(x)\n# break\n \n# locations.append(x)\n\n# print(\"Part 2 answer:\")\n# print(min(locations))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nseed: 1310704671\nseed: 312415190\nseed: 1034820096\nseed: 106131293\nseed: 682397438\nseed: 30365957\nseed: 2858337556\nseed: 1183890307\nseed: 665754577\nseed: 13162298\nseed: 2687187253\nseed: 74991378\nseed: 1782124901\nseed: 3190497\nseed: 208902075\nseed: 226221606\nseed: 4116455504\nseed: 87808390\nseed: 2403629707\nseed: 66592398\nPart 1 answer:\n51752125\n```\n:::\n:::\n\n\n:::\n## 6\nThere's definitely a more efficient way of doing part 2, but good enough :)\n\n::: {.column-page}\n\n::: {.cell execution_count=6}\n``` {.python .cell-code}\nimport math\n\nwith open(\"aoc/6/input.txt\", \"r\") as f:\n inp = f.readlines()\n \ntime = map(int, filter(lambda x: x.isnumeric(), inp[0].replace(\"Time:\", \"\").strip().split(\" \")))\ndist = map(int, filter(lambda x: x.isnumeric(), inp[1].replace(\"Distance:\", \"\").strip().split(\" \")))\ntotals = []\n\nfor t, d_record in zip(time, dist):\n total = 0\n for i in range(t):\n v = i\n d = v * (t - i)\n \n if d > d_record:\n total += 1\n \n totals.append(total)\n \nprint(\"Part 1 answer:\")\nprint(math.prod(totals))\n\n\ntime = int(inp[0].replace(\"Time:\", \"\").replace(\" \", \"\"))\ndist = int(inp[1].replace(\"Distance:\", \"\").replace(\" \", \"\"))\ntotals = []\n\nt = time\nd_record = dist\n\ntotal = 0\nfor i in range(t):\n v = i\n d = v * (t - i)\n \n if d > d_record:\n total += 1\n \ntotals.append(total)\n\nprint(\"Part 2 answer:\")\nprint(math.prod(totals))\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nPart 1 answer:\n625968\nPart 2 answer:\n43663323\n```\n:::\n:::\n\n\n:::\n\n", "supporting": [ "aoc_files" ], diff --git a/aoc/aoc.qmd b/aoc/aoc.qmd index 1331dd3..341c0c8 100644 --- a/aoc/aoc.qmd +++ b/aoc/aoc.qmd @@ -260,3 +260,140 @@ print("Part 2 answer:") print(total) ``` ::: +## 5 +Stuck on part 2 for now... + +::: {.column-page} +```{python} +from collections import OrderedDict +from typing import Any + +with open("aoc/5/input.txt", "r") as f: + inp = f.readlines() + + +# create maps +seeds = list(map(int, inp.pop(0).replace("seeds: ", "").strip().split(" "))) +maps = OrderedDict() + +class Mapper: + def __init__(self, dest, source, rng): + self.dest = dest + self.source = source + self.rng = rng + + def check(self, x): + return self.source <= x < (self.source + self.rng) + + def __call__(self, x) -> Any: + return x + (self.dest - self.source) + + def __repr__(self): + return f"{self.dest=}|{self.source=}|{self.rng=}" + + +for line in inp: + if line == "\n": + continue + + if "map" in line: + map_name = line.replace("\n", "").replace(" map:", "") + + else: + dest, source, rng = map(int, line.replace("\n", "").split(" ")) + + maps.setdefault(map_name, []).append( + Mapper(dest, source, rng) + ) + +locations = [] + +for x in seeds: + print("seed:", x) + for k in maps.keys(): + current_map = maps[k] + + for f in current_map: + if f.check(x): + x = f(x) + break + + locations.append(x) + +print("Part 1 answer:") +print(min(locations)) + + +# This is incredibly slow as too many ints to check! +# Need to try another approach using ranges! +# locations = [] + +# for z, y in zip(seeds[::2], seeds[1::2]): +# for x in range(z, z+y): +# # print("seed:", x) +# for k in maps.keys(): +# current_map = maps[k] + +# for f in current_map: +# if f.check(x): +# x = f(x) +# break + +# locations.append(x) + +# print("Part 2 answer:") +# print(min(locations)) + +``` +::: +## 6 +There's definitely a more efficient way of doing part 2, but good enough :) + +::: {.column-page} +```{python} +import math + +with open("aoc/6/input.txt", "r") as f: + inp = f.readlines() + +time = map(int, filter(lambda x: x.isnumeric(), inp[0].replace("Time:", "").strip().split(" "))) +dist = map(int, filter(lambda x: x.isnumeric(), inp[1].replace("Distance:", "").strip().split(" "))) +totals = [] + +for t, d_record in zip(time, dist): + total = 0 + for i in range(t): + v = i + d = v * (t - i) + + if d > d_record: + total += 1 + + totals.append(total) + +print("Part 1 answer:") +print(math.prod(totals)) + + +time = int(inp[0].replace("Time:", "").replace(" ", "")) +dist = int(inp[1].replace("Distance:", "").replace(" ", "")) +totals = [] + +t = time +d_record = dist + +total = 0 +for i in range(t): + v = i + d = v * (t - i) + + if d > d_record: + total += 1 + +totals.append(total) + +print("Part 2 answer:") +print(math.prod(totals)) + +``` +::: diff --git a/aoc/create_quarto_doc.py b/aoc/create_quarto_doc.py index 57f5676..93216d3 100644 --- a/aoc/create_quarto_doc.py +++ b/aoc/create_quarto_doc.py @@ -22,6 +22,8 @@ 2: "Feel like this should have been day one 😄", 3: "I don't like grids 🫠 I probably made this harder than it needed to be. If I were to do this again I probably would have just used euclidian distance comparisons", 4: "Enjoyed the logic for the second part with the copies. I'm sure there was potential to go on a wild goose chase with recursion here, so I'm happy to have avoided the temptation 🤣", + 5: "Stuck on part 2 for now...", + 6: "There's definitely a more efficient way of doing part 2, but good enough :)" }