Day 18 (Python)
Day 18, part one (Python) Day 18, part two (Python) Day 18 READMEs
This commit is contained in:
parent
fa8bda45d8
commit
8d2eeee6fa
6 changed files with 297 additions and 1 deletions
2
.github/README.md
vendored
2
.github/README.md
vendored
|
@ -33,7 +33,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
|
|||
| [15](/15-rambunctiousRecitation) \* | ![Completed][check] | [Link](/15-rambunctiousRecitation/python) | [Link](/15-rambunctiousRecitation/go) |
|
||||
| [16](/16-ticketTranslation) | ![Partially complete][partial] | [Link](/16-ticketTranslation/python) | |
|
||||
| [17](/17-conwayCubes) | ![Partially complete][partial] | [Link](/17-conwayCubes/python) | |
|
||||
| 18 | ![Not yet attempted][pending] | | |
|
||||
| [18](/18-operationOrder) | ![Partially complete][partial] | [Link](/18-operationOrder/python) | |
|
||||
| 19 | | | |
|
||||
| 20 | | | |
|
||||
| 21 | | | |
|
||||
|
|
59
18-operationOrder/README.md
Normal file
59
18-operationOrder/README.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
# [Day 18: Operation Order](https://adventofcode.com/2020/day/18)
|
||||
|
||||
### Part one
|
||||
|
||||
Each expression is represented by a list. Each set of brackets in an input is a nested list within that list. For example:
|
||||
|
||||
```py
|
||||
>>> parse_expression("1 + 1 + (3 * 4) + 8".replace(" ", ""))[0]
|
||||
['1', '+', '1', '+', ['3', '*', '4'], '+', '8']
|
||||
```
|
||||
|
||||
In order to calculate this, a recursive function is used to step through each item and perform any operations found in a linear manner (ie, ignoring [operator precedence](https://en.wikipedia.org/wiki/Order_of_operations)).
|
||||
|
||||
### Part two
|
||||
|
||||
Attempts to use the same data structure for part two, while possible, were difficult and I never actually ended up getting any of them working. I tried two different methods.
|
||||
|
||||
* Encasing every single addition operation in a set of brackets (this was done after parsing, so it was actually replacing three items from a list with a list of those three items)
|
||||
* Selectivley calculating only the additions in an expression before then going on to calculate the multiplication
|
||||
* This isn't actually possible - how are you meant to calculate `1 + (2 + 3 * 4)` without multiplication?
|
||||
|
||||
After I started wanting to punch myself repeatedly in the head because of the amount of issues I was having with my own solution, I gave up and found an implentation of the shunting-yard algorithm ([this one](http://www.martinbroadhurst.com/shunting-yard-algorithm-in-python.html), to be precise), modified the operator precendences and used that.
|
||||
|
||||
It worked first try. *sigh*
|
||||
|
||||
At some point, I'd like to come back to this and write my own implementation, and convert the first part to using that new implementation.
|
||||
|
||||
### Related
|
||||
|
||||
* [Wikipedia - Shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm)
|
||||
* [Computerphile - Reverse Polish Notation and The Stack](https://www.youtube.com/watch?v=7ha78yWRDlE)
|
||||
|
||||
<details><summary>Script output</summary>
|
||||
|
||||
```
|
||||
❯ python .\python\
|
||||
AoC 2020: day 18 - Operation Order
|
||||
Python 3.8.5
|
||||
|
||||
Test cases
|
||||
1.1 pass
|
||||
1.2 pass
|
||||
1.3 pass
|
||||
1.4 pass
|
||||
1.5 pass
|
||||
1.6 pass
|
||||
2.1 pass
|
||||
2.2 pass
|
||||
2.3 pass
|
||||
2.4 pass
|
||||
2.5 pass
|
||||
2.6 pass
|
||||
|
||||
Answers
|
||||
Part 1: 18213007238947
|
||||
Part 2: 388966573054664
|
||||
```
|
||||
|
||||
</details>
|
23
18-operationOrder/info.json
Normal file
23
18-operationOrder/info.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"year": "2020",
|
||||
"day": "18",
|
||||
"title": "Operation Order",
|
||||
"testCases": {
|
||||
"one": [
|
||||
{"input":"1 + 2 * 3 + 4 * 5 + 6", "expected":71},
|
||||
{"input":"1 + (2 * 3) + (4 * (5 + 6))", "expected":51},
|
||||
{"input":"2 * 3 + (4 * 5)", "expected":26},
|
||||
{"input":"5 + (8 * 3 + 9 + 3 * 4 * 3)", "expected":437},
|
||||
{"input":"5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))", "expected":12240},
|
||||
{"input":"((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2", "expected":13632}
|
||||
],
|
||||
"two": [
|
||||
{"input":"1 + 2 * 3 + 4 * 5 + 6", "expected":231},
|
||||
{"input":"1 + (2 * 3) + (4 * (5 + 6))", "expected": 51},
|
||||
{"input":"2 * 3 + (4 * 5)", "expected": 46},
|
||||
{"input":"5 + (8 * 3 + 9 + 3 * 4 * 3)", "expected": 1445},
|
||||
{"input":"5 * 9 * (7 * 3 * 3 + 9 * 3 + (8 + 6 * 4))", "expected": 669060},
|
||||
{"input":"((2 + 4 * 9) * (6 + 9 * 8 + 6) + 6) + 2 + 4 * 2", "expected": 23340}
|
||||
]
|
||||
}
|
||||
}
|
77
18-operationOrder/python/__main__.py
Normal file
77
18-operationOrder/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))
|
67
18-operationOrder/python/partOne.py
Normal file
67
18-operationOrder/python/partOne.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from typing import List, Tuple, Union
|
||||
|
||||
|
||||
def parse_expression(expression: str) -> Tuple[List, int]:
|
||||
o = []
|
||||
|
||||
if len(expression) == 0:
|
||||
return o, 0
|
||||
|
||||
ptr = 0
|
||||
while ptr < len(expression):
|
||||
char = expression[ptr]
|
||||
if char == "(":
|
||||
nl, idx = parse_expression(expression[ptr + 1 :])
|
||||
o.append(nl)
|
||||
ptr += idx
|
||||
elif char == ")":
|
||||
return o, ptr + 1
|
||||
else:
|
||||
o.append(char)
|
||||
|
||||
ptr += 1
|
||||
|
||||
return o, 0
|
||||
|
||||
|
||||
def calc_to_int(x: Union[list, str]) -> int:
|
||||
if type(x) == list:
|
||||
return calculate(x)
|
||||
else:
|
||||
return int(x)
|
||||
|
||||
|
||||
def calculate(expression: List) -> int:
|
||||
n = calc_to_int(expression[0])
|
||||
|
||||
ptr = 1
|
||||
while ptr < len(expression):
|
||||
op = expression[ptr]
|
||||
x = calc_to_int(expression[ptr + 1])
|
||||
|
||||
if op == "+":
|
||||
n += x
|
||||
elif op == "-":
|
||||
n -= x
|
||||
elif op == "*":
|
||||
n *= x
|
||||
elif op == "/":
|
||||
n /= x
|
||||
else:
|
||||
raise ValueError(f"unknown operator '{op}'")
|
||||
ptr += 2
|
||||
|
||||
return n
|
||||
|
||||
|
||||
def partOne(instr: str) -> int:
|
||||
expressions = [
|
||||
parse_expression(list(x.replace(" ", "")))[0] for x in instr.strip().split("\n")
|
||||
]
|
||||
|
||||
sigma = 0
|
||||
|
||||
for expression in expressions:
|
||||
sigma += calculate(expression)
|
||||
|
||||
return sigma
|
70
18-operationOrder/python/partTwo.py
Normal file
70
18-operationOrder/python/partTwo.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import re
|
||||
|
||||
|
||||
def is_number(str):
|
||||
try:
|
||||
int(str)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def is_name(str):
|
||||
return re.match("\w+", str)
|
||||
|
||||
|
||||
def peek(stack):
|
||||
return stack[-1] if stack else None
|
||||
|
||||
|
||||
def apply_operator(operators, values):
|
||||
operator = operators.pop()
|
||||
right = values.pop()
|
||||
left = values.pop()
|
||||
values.append(eval("{0}{1}{2}".format(left, operator, right)))
|
||||
|
||||
|
||||
def greater_precedence(op1, op2):
|
||||
precedences = {"+": 2, "-": 0, "*": 1, "/": 1}
|
||||
return precedences[op1] > precedences[op2]
|
||||
|
||||
|
||||
def evaluate(expression):
|
||||
tokens = re.findall("[+/*()-]|\d+", expression)
|
||||
values = []
|
||||
operators = []
|
||||
for token in tokens:
|
||||
if is_number(token):
|
||||
values.append(int(token))
|
||||
elif token == "(":
|
||||
operators.append(token)
|
||||
elif token == ")":
|
||||
top = peek(operators)
|
||||
while top is not None and top != "(":
|
||||
apply_operator(operators, values)
|
||||
top = peek(operators)
|
||||
operators.pop() # Discard the '('
|
||||
else:
|
||||
# Operator
|
||||
top = peek(operators)
|
||||
while (
|
||||
top is not None and top not in "()" and greater_precedence(top, token)
|
||||
):
|
||||
apply_operator(operators, values)
|
||||
top = peek(operators)
|
||||
operators.append(token)
|
||||
while peek(operators) is not None:
|
||||
apply_operator(operators, values)
|
||||
|
||||
return values[0]
|
||||
|
||||
|
||||
def partTwo(instr: str) -> int:
|
||||
expressions = instr.strip().split("\n")
|
||||
|
||||
sigma = 0
|
||||
|
||||
for expression in expressions:
|
||||
sigma += evaluate(expression)
|
||||
|
||||
return sigma
|
Loading…
Add table
Add a link
Reference in a new issue