Day 16 (Python)
This commit is contained in:
parent
d7fa3442a1
commit
3f24372cc4
7 changed files with 231 additions and 1 deletions
2
.github/README.md
vendored
2
.github/README.md
vendored
|
@ -31,7 +31,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
|
||||||
| [13](/13-shuttleSearch) | ![Partially complete][partial] | [Link](/13-shuttleSearch/python) | |
|
| [13](/13-shuttleSearch) | ![Partially complete][partial] | [Link](/13-shuttleSearch/python) | |
|
||||||
| [14](/14-dockingData) | ![Completed][check] | [Link](/14-dockingData/python) | [Link](/14-dockingData/go) |
|
| [14](/14-dockingData) | ![Completed][check] | [Link](/14-dockingData/python) | [Link](/14-dockingData/go) |
|
||||||
| [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) |
|
| [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) |
|
||||||
| 16 | ![Not yet attempted][pending] | | |
|
| [16](/16-ticketTranslation) | ![Partially complete][partial] | [Link](/16-ticketTranslation/python) | |
|
||||||
| 17 | | | |
|
| 17 | | | |
|
||||||
| 18 | | | |
|
| 18 | | | |
|
||||||
| 19 | | | |
|
| 19 | | | |
|
||||||
|
|
21
16-ticketTranslation/README.md
Normal file
21
16-ticketTranslation/README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# [Day 16: Ticket Translation](https://adventofcode.com/2020/day/16)
|
||||||
|
|
||||||
|
It took me a long while to realise each column could have multiple different possibilities for which ticket field it could be.
|
||||||
|
|
||||||
|
<details><summary>Script output</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ python .\python\
|
||||||
|
AoC 2020: day 16 - Ticket Translation
|
||||||
|
Python 3.8.5
|
||||||
|
|
||||||
|
Test cases
|
||||||
|
1.1 pass
|
||||||
|
2.1 pass
|
||||||
|
|
||||||
|
Answers
|
||||||
|
Part 1: 29019
|
||||||
|
Part 2: 517827547723
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
19
16-ticketTranslation/info.json
Normal file
19
16-ticketTranslation/info.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"year": "2020",
|
||||||
|
"day": "16",
|
||||||
|
"title": "Ticket Translation",
|
||||||
|
"testCases": {
|
||||||
|
"one": [
|
||||||
|
{
|
||||||
|
"input": "class: 1-3 or 5-7\nrow: 6-11 or 33-44\nseat: 13-40 or 45-50\n\nyour ticket:\n7,1,14\n\nnearby tickets:\n7,3,47\n40,4,50\n55,2,20\n38,6,12",
|
||||||
|
"expected": 71
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"two": [
|
||||||
|
{
|
||||||
|
"input":"departure class: 0-1 or 4-19\nrow: 0-5 or 8-19\nseat: 0-13 or 16-19\n\nyour ticket:\n11,12,13\n\nnearby tickets:\n3,9,18\n15,1,5\n5,14,9\n",
|
||||||
|
"expected": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
77
16-ticketTranslation/python/__main__.py
Normal file
77
16-ticketTranslation/python/__main__.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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:", partOne(challenge_input))
|
||||||
|
print("Part 2:", partTwo(challenge_input))
|
51
16-ticketTranslation/python/common.py
Normal file
51
16-ticketTranslation/python/common.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
class Rule:
|
||||||
|
name: str
|
||||||
|
ranges: Tuple[int, ...]
|
||||||
|
|
||||||
|
def __init__(self, instr:str) -> None:
|
||||||
|
# arrival location: 26-482 or 504-959
|
||||||
|
field, conditions = instr.strip().split(": ")
|
||||||
|
self.name = field
|
||||||
|
ranges = conditions.split("or")
|
||||||
|
self.ranges = tuple([int(x) for x in ranges[0].strip().split("-")] + [int(x) for x in ranges[1].strip().split("-")])
|
||||||
|
|
||||||
|
class Ticket:
|
||||||
|
fields: Tuple[int]
|
||||||
|
|
||||||
|
def __init__(self, instr:str) -> None:
|
||||||
|
self.fields = tuple([int(x) for x in instr.strip().split(",")])
|
||||||
|
|
||||||
|
def parse(instr: str) -> Tuple[List[Rule], Ticket, List[Ticket]]:
|
||||||
|
|
||||||
|
raw_rules, raw_my_ticket, raw_other_tickets = instr.strip().split("\n\n")
|
||||||
|
|
||||||
|
rules = [Rule(x) for x in raw_rules.split("\n")]
|
||||||
|
|
||||||
|
my_ticket = Ticket(raw_my_ticket.split("\n")[-1])
|
||||||
|
|
||||||
|
other_tickets = [Ticket(x) for x in raw_other_tickets.strip("nearby tickets:\n").split("\n")]
|
||||||
|
|
||||||
|
return rules, my_ticket, other_tickets
|
||||||
|
|
||||||
|
def test_value(value:int, condition:Tuple[int, ...]) -> bool:
|
||||||
|
return condition[0] <= value <= condition[1] or condition[2] <= value <= condition[3]
|
||||||
|
|
||||||
|
def find_invalid(rules:List[Rule], tickets:List[Ticket]) -> Tuple[List[int], List[int]]:
|
||||||
|
# returns invalid values and indexes of invalid tickets
|
||||||
|
|
||||||
|
invalid_values = []
|
||||||
|
invalid_indexes = []
|
||||||
|
|
||||||
|
for i, ticket in enumerate(tickets):
|
||||||
|
for field in ticket.fields:
|
||||||
|
field_is_valid = False
|
||||||
|
for rule in rules:
|
||||||
|
if test_value(field, rule.ranges):
|
||||||
|
field_is_valid = True
|
||||||
|
if not field_is_valid:
|
||||||
|
invalid_values.append(field)
|
||||||
|
invalid_indexes.append(i)
|
||||||
|
|
||||||
|
return invalid_values, invalid_indexes
|
9
16-ticketTranslation/python/partOne.py
Normal file
9
16-ticketTranslation/python/partOne.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from common import *
|
||||||
|
|
||||||
|
|
||||||
|
def partOne(instr: str) -> int:
|
||||||
|
rules, _, tickets = parse(instr)
|
||||||
|
|
||||||
|
invalid_values, _ = find_invalid(rules, tickets)
|
||||||
|
|
||||||
|
return sum(invalid_values)
|
53
16-ticketTranslation/python/partTwo.py
Normal file
53
16-ticketTranslation/python/partTwo.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
from common import *
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
def partTwo(instr: str) -> int:
|
||||||
|
rules, my_ticket, tickets = parse(instr)
|
||||||
|
|
||||||
|
# purge bad indexes
|
||||||
|
_, invalid_indexes = find_invalid(rules, tickets)
|
||||||
|
invalid_indexes = list(reversed(sorted(invalid_indexes)))
|
||||||
|
|
||||||
|
for idx in invalid_indexes:
|
||||||
|
tickets.pop(idx)
|
||||||
|
|
||||||
|
ticket_length = len(tickets[0].fields)
|
||||||
|
num_tickets = len(tickets)
|
||||||
|
num_rules = len(rules)
|
||||||
|
|
||||||
|
candidates = {rule.name: set() for rule in rules}
|
||||||
|
|
||||||
|
for col in range(ticket_length):
|
||||||
|
values = [ticket.fields[col] for ticket in tickets]
|
||||||
|
|
||||||
|
for rule_idx, rule in enumerate(rules):
|
||||||
|
complete_match = True
|
||||||
|
for v in values:
|
||||||
|
if not test_value(v, rule.ranges):
|
||||||
|
complete_match = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if complete_match:
|
||||||
|
candidates[rule.name].add(col)
|
||||||
|
|
||||||
|
parameter_indexes = {}
|
||||||
|
removed = set()
|
||||||
|
|
||||||
|
product = 1
|
||||||
|
|
||||||
|
for col in range(ticket_length):
|
||||||
|
for name in candidates:
|
||||||
|
candidates_set = candidates[name] - removed
|
||||||
|
|
||||||
|
if len(candidates_set) == 1:
|
||||||
|
idx = candidates_set.pop()
|
||||||
|
parameter_indexes[name] = idx
|
||||||
|
removed.add(idx)
|
||||||
|
|
||||||
|
product = 1
|
||||||
|
for param in parameter_indexes:
|
||||||
|
if "departure" in param:
|
||||||
|
product *= my_ticket.fields[parameter_indexes[param]]
|
||||||
|
|
||||||
|
return product
|
Loading…
Add table
Add a link
Reference in a new issue