Day 21 (Python)

This commit is contained in:
akp 2020-12-21 13:03:43 +00:00
parent 5a5b902009
commit 93082b4cd9
No known key found for this signature in database
GPG key ID: D3E7EAA31B39637E
7 changed files with 225 additions and 1 deletions

2
.github/README.md vendored
View file

@ -36,7 +36,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
| [18](/18-operationOrder) | ![Partially complete][partial] | [Link](/18-operationOrder/python) | | |
| [19](/19-monsterMessages) | ![Completed][check] | [Link](/19-monsterMessages/python) | [Link](/19-monsterMessages/go) | |
| [20](/20-jurassicJigsaw) | ![Partially complete][partial] | [Link](/20-jurassicJigsaw/python) | | Only part one solved |
| 21 | ![Not yet attempted][pending] | | | |
| [21](/21-allergenAmusement) | ![Partially complete][partial] | [Link](/21-allergenAmusement/python) | | |
| 22 | | | | |
| 23 | | | | |
| 24 | | | | |

View file

@ -0,0 +1,23 @@
# [Day 21: Allergen Amusement](https://adventofcode.com/2020/day/21)
This one isn't going to be translated to go because **a)** Go has no sets and **b)** Go is statically typed and I cannot be bothered to try and tweak the return type for part two.
*I made the assumption we'd only ever have numeric solutions when I built the boilerplate, goddamnit!*
<details><summary>Script output</summary>
```
python .\python\
AoC 2020: day 21 - Allergen Amusement
Python 3.8.5
Test cases
1.1 pass
2.1 pass
Answers
Part 1: 2307
Part 2: cljf,frtfg,vvfjj,qmrps,hvnkk,qnvx,cpxmpc,qsjszn
```
</details>

View file

@ -0,0 +1,19 @@
{
"year": "2020",
"day": "21",
"title": "Allergen Amusement",
"testCases": {
"one": [
{
"input": "mxmxvkd kfcds sqjhc nhms (contains dairy, fish)\ntrh fvjkl sbzzf mxmxvkd (contains dairy)\nsqjhc fvjkl (contains soy)\nsqjhc mxmxvkd sbzzf (contains fish)\n",
"expected": 5
}
],
"two": [
{
"input": "mxmxvkd kfcds sqjhc nhms (contains dairy, fish)\ntrh fvjkl sbzzf mxmxvkd (contains dairy)\nsqjhc fvjkl (contains soy)\nsqjhc mxmxvkd sbzzf (contains fish)\n",
"expected": "mxmxvkd,sqjhc,fvjkl"
}
]
}
}

View file

@ -0,0 +1,79 @@
import json
import platform
import sys
from rich import print
from partOne import partOne
from partTwo import partTwo
def run_tests(test_cases):
do_tests = True
if len(test_cases) == 0:
do_tests = False
elif len(test_cases["one"]) == 0 and len(test_cases["two"]) == 0:
do_tests = False
if not do_tests:
print("Info: no test cases specified. Skipping tests\n")
return
print("Test cases")
def rt(tcs, f, n):
for i, tc in enumerate(tcs):
print(f"{n}.{i+1} ", end="")
expectedInt = tc["expected"]
result = f(str(tc["input"]))
if result == expectedInt:
print("[green]pass[/green]")
else:
print(f"[red]fail[/red] (got {result}, expected {expectedInt})")
rt(test_cases["one"], partOne, 1)
rt(test_cases["two"], partTwo, 2)
print()
if __name__ == "__main__":
try:
info = open("info.json").read()
except FileNotFoundError:
print("Error: could not open info.json")
sys.exit(-1)
info = json.loads(info)
year = info["year"]
day = info["day"]
title = info["title"]
print(f"[yellow]AoC {year}[/yellow]: day {day} - {title}")
print(f"Python {platform.python_version()}\n")
try:
challenge_input = open("input.txt").read()
except FileNotFoundError:
print("Error: could not open input.txt")
sys.exit(-1)
if "vis" in sys.argv:
import visualise
print("[green]Running visualisation....[/green]")
visualise.visualise(challenge_input)
sys.exit()
run_tests(info["testCases"])
if "debug" in sys.argv:
sys.exit()
print("Answers")
print("Part 1: ", end="")
print(partOne(challenge_input))
print("Part 2: ", end="")
print(partTwo(challenge_input))

View file

@ -0,0 +1,46 @@
from typing import List, Set, Tuple, Dict
import re
class Food:
ingredients: List[str]
known_allergens: Set[str]
def __init__(self, ingredients: List[str], known_allergens: Set[str]) -> None:
self.ingredients = ingredients
self.known_allergens = known_allergens
def parse(instr: str) -> Tuple[List[Food], Set[str]]:
foods = []
all_allergens = set()
for x in instr.strip().split("\n"):
m = re.match(r"(.+) \(contains (.+)\)", x)
if m is None:
raise ValueError("input string does not match required format")
ingredients = m.group(1).split(" ")
allergens = set(m.group(2).split(", "))
all_allergens = all_allergens.union(allergens)
foods.append(Food(ingredients, allergens))
return foods, all_allergens
def get_possibilities(foods: List[Food], allergens: Set[str]) -> Dict[str, Set[str]]:
possibilities = {n: set() for n in allergens}
for food in foods:
for known_allergen in food.known_allergens:
if len(possibilities[known_allergen]) == 0:
possibilities[known_allergen] = set(food.ingredients)
else:
possibilities[known_allergen] = (
set(food.ingredients) & possibilities[known_allergen]
)
return possibilities

View file

@ -0,0 +1,18 @@
from common import *
def partOne(instr: str) -> int:
foods, allergens = parse(instr)
possibilities = get_possibilities(foods, allergens)
bad_ingredients = set()
for x in possibilities:
bad_ingredients.update(possibilities[x])
count = 0
for food in foods:
for ingredient in food.ingredients:
if ingredient not in bad_ingredients:
count += 1
return count

View file

@ -0,0 +1,39 @@
from common import *
def partTwo(instr: str) -> None:
foods, allergens = parse(instr)
possibilities = get_possibilities(foods, allergens)
definites = {} # ingredients that definitely contain allergens
# remove known things
for possibility in possibilities:
if len(possibilities[possibility]) == 1:
definites[possibility] = possibilities[possibility].pop()
for definite in definites:
del possibilities[definite]
while True:
definite_set = set([definites[x] for x in definites])
if len(possibilities) == 0:
break
to_del = []
for possibility in possibilities:
possibilities[possibility] = possibilities[possibility] - definite_set
if len(possibilities[possibility]) == 0:
to_del.append(possibility)
elif len(possibilities[possibility]) == 1:
definites[possibility] = possibilities[possibility].pop()
for td in to_del:
del possibilities[td]
return ",".join(
[
x[1]
for x in sorted([(x, definites[x]) for x in definites], key=lambda x: x[0])
]
)