Day 12 (Python)
This commit is contained in:
parent
f5afabaadf
commit
bdba0ff639
7 changed files with 339 additions and 1 deletions
2
.github/README.md
vendored
2
.github/README.md
vendored
|
@ -27,7 +27,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
|
|||
| [9](/09-encodingError) | ![Completed][check] | [Link](/09-encodingError/python) | [Link](/09-encodingError/go) |
|
||||
| [10](/10-adapterArray) | ![Completed][check] | [Link](/10-adapterArray/python) | [Link](/10-adapterArray/go) |
|
||||
| [11](/11-seatingSystem) | ![Partially complete][partial] | [Link](/11-seatingSystem/python) | |
|
||||
| 12 | ![Not yet attempted][pending] | | |
|
||||
| [12](/12-rainRisk) | ![Partially complete][partial] | [Link](/12-rainRisk/python) | |
|
||||
| 13 | | | |
|
||||
| 14 | | | |
|
||||
| 15 | | | |
|
||||
|
|
19
12-rainRisk/README.md
Normal file
19
12-rainRisk/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# [Day 12: Rain Risk](https://adventofcode.com/2020/day/12)
|
||||
|
||||
<details><summary>Script output</summary>
|
||||
|
||||
```
|
||||
❯ python .\python\
|
||||
AoC 2020: day 12 - Rain Risk
|
||||
Python 3.8.5
|
||||
|
||||
Test cases
|
||||
1.1 pass
|
||||
2.1 pass
|
||||
|
||||
Answers
|
||||
Part 1: 1645
|
||||
Part 2: 35292
|
||||
```
|
||||
|
||||
</details>
|
19
12-rainRisk/info.json
Normal file
19
12-rainRisk/info.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"year": "2020",
|
||||
"day": "12",
|
||||
"title": "Rain Risk",
|
||||
"testCases": {
|
||||
"one": [
|
||||
{
|
||||
"input": "F10\nN3\nF7\nR90\nF11",
|
||||
"expected": 25
|
||||
}
|
||||
],
|
||||
"two": [
|
||||
{
|
||||
"input": "F10\nN3\nF7\nR90\nF11",
|
||||
"expected": 286
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
69
12-rainRisk/python/__main__.py
Normal file
69
12-rainRisk/python/__main__.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
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)
|
||||
|
||||
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))
|
38
12-rainRisk/python/common.py
Normal file
38
12-rainRisk/python/common.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from typing import List, Tuple
|
||||
|
||||
|
||||
class Instruction:
|
||||
action: str
|
||||
magnitude: str
|
||||
raw: str
|
||||
|
||||
def __init__(self, instruction:str) -> None:
|
||||
self.action = instruction[0].lower()
|
||||
self.magnitude = int(instruction[1:])
|
||||
self.raw = instruction
|
||||
|
||||
|
||||
def parse(instr: str) -> List[Instruction]:
|
||||
return [Instruction(x) for x in instr.strip().split("\n")]
|
||||
|
||||
|
||||
def calculate_direction_deltas(direction:str, amount:int) -> Tuple[int, int]:
|
||||
# returns a pair of deltas representing lat,long
|
||||
lat_delta = 0
|
||||
long_delta = 0
|
||||
|
||||
if direction == "n":
|
||||
lat_delta += amount
|
||||
elif direction == "s":
|
||||
lat_delta -= amount
|
||||
|
||||
elif direction == "e":
|
||||
long_delta += amount
|
||||
elif direction == "w":
|
||||
long_delta -= amount
|
||||
|
||||
else:
|
||||
raise AssertionError(f"invalid direction '{direction}'")
|
||||
|
||||
return lat_delta, long_delta
|
||||
|
77
12-rainRisk/python/partOne.py
Normal file
77
12-rainRisk/python/partOne.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
from typing import Tuple
|
||||
|
||||
from common import *
|
||||
|
||||
|
||||
bearings_num = {
|
||||
0: "n",
|
||||
90: "e",
|
||||
180: "s",
|
||||
270: "w",
|
||||
}
|
||||
|
||||
bearings_ltr = {
|
||||
"n": 0,
|
||||
"e": 90,
|
||||
"s": 180,
|
||||
"w": 270,
|
||||
}
|
||||
|
||||
|
||||
def rotate_direction(current:str, direction:str, amount:int) -> str:
|
||||
|
||||
assert direction in ["l", "r"], f"invalid rotate direction '{direction}'"
|
||||
|
||||
current_bearing = bearings_ltr[current]
|
||||
|
||||
if direction == "l":
|
||||
current_bearing -= amount
|
||||
elif direction == "r":
|
||||
current_bearing += amount
|
||||
|
||||
if current_bearing >= 360:
|
||||
current_bearing -= 360
|
||||
elif current_bearing < 0:
|
||||
current_bearing += 360
|
||||
|
||||
return bearings_num[current_bearing]
|
||||
|
||||
|
||||
def translate_movement(current_direction:str, instruction:Instruction) -> Tuple[str, int, int]:
|
||||
# Returns the new current direction and the lat/long delta
|
||||
|
||||
lat_delta = 0
|
||||
long_delta = 0
|
||||
|
||||
if instruction.action in ["l", "r"]:
|
||||
current_direction = rotate_direction(current_direction, instruction.action, instruction.magnitude)
|
||||
elif instruction.action == "f":
|
||||
lat_delta, long_delta = calculate_direction_deltas(current_direction, instruction.magnitude)
|
||||
elif instruction.action in ["n", "s", "e", "w"]:
|
||||
lat_delta, long_delta = calculate_direction_deltas(instruction.action, instruction.magnitude)
|
||||
else:
|
||||
raise AssertionError(f"invalid action '{instruction.action}'")
|
||||
|
||||
return current_direction, lat_delta, long_delta
|
||||
|
||||
|
||||
def partOne(instr: str) -> int:
|
||||
input_list = parse(instr)
|
||||
|
||||
current_direction = "e"
|
||||
lat = 0 # north/south, pos/neg
|
||||
long = 0 # west/east, pos/neg
|
||||
|
||||
for instruction in input_list:
|
||||
current_direction, lad, lod = translate_movement(current_direction, instruction)
|
||||
lat += lad
|
||||
long += lod
|
||||
|
||||
# Calculate manhattan distance
|
||||
# If either value is negative, make it positive
|
||||
if lat < 0:
|
||||
lat = lat + -2*lat
|
||||
if long < 0:
|
||||
long = long + -2*long
|
||||
|
||||
return lat + long
|
116
12-rainRisk/python/partTwo.py
Normal file
116
12-rainRisk/python/partTwo.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
from typing import Tuple
|
||||
|
||||
from common import *
|
||||
|
||||
|
||||
def make_positive(n:int) -> int:
|
||||
return n if n >= 0 else n + -2*n
|
||||
|
||||
|
||||
def make_negative(n:int) -> int:
|
||||
return n if n < 0 else n + -2*n
|
||||
|
||||
|
||||
def rotate_waypoint(current:Tuple[int, int], direction:str, amount:int) -> Tuple[int, int]:
|
||||
|
||||
assert direction in ["l", "r"], f"invalid rotate direction '{direction}'"
|
||||
|
||||
lat_delta, long_delta = current
|
||||
|
||||
times = int(amount / 90) # number of times to rotate the waypoint by
|
||||
|
||||
# Determine the current quadrant
|
||||
quadrant = None
|
||||
if lat_delta >= 0 and long_delta >= 0:
|
||||
quadrant = 1
|
||||
elif lat_delta < 0 and long_delta >= 0:
|
||||
quadrant = 2
|
||||
elif lat_delta < 0 and long_delta < 0:
|
||||
quadrant = 3
|
||||
elif lat_delta >= 0 and long_delta < 0:
|
||||
quadrant = 4
|
||||
|
||||
assert quadrant is not None, f"unable to determine quadrant for {current}"
|
||||
|
||||
# Determine the new quadrant
|
||||
new_quadrant = quadrant
|
||||
if direction == "r":
|
||||
new_quadrant += times
|
||||
elif direction == "l":
|
||||
new_quadrant -= times
|
||||
|
||||
# Loop quadrant around if it went outside of 1-4
|
||||
if new_quadrant > 4:
|
||||
new_quadrant -= 4
|
||||
elif new_quadrant < 1:
|
||||
new_quadrant += 4
|
||||
|
||||
# Swap values if the new quadrant is not the opposite one
|
||||
quadrant_diff = quadrant - new_quadrant
|
||||
if quadrant_diff % 2 != 0:
|
||||
t = lat_delta
|
||||
lat_delta = long_delta
|
||||
long_delta = t
|
||||
|
||||
# Transform deltas into their new quadrant
|
||||
if new_quadrant == 1:
|
||||
lat_delta = make_positive(lat_delta)
|
||||
long_delta = make_positive(long_delta)
|
||||
elif new_quadrant == 2:
|
||||
lat_delta = make_negative(lat_delta)
|
||||
long_delta = make_positive(long_delta)
|
||||
elif new_quadrant == 3:
|
||||
lat_delta = make_negative(lat_delta)
|
||||
long_delta = make_negative(long_delta)
|
||||
elif new_quadrant == 4:
|
||||
lat_delta = make_positive(lat_delta)
|
||||
long_delta = make_negative(long_delta)
|
||||
|
||||
return lat_delta, long_delta
|
||||
|
||||
|
||||
def move_to_waypoint(waypoint_delta:Tuple[int, int], times:int) -> Tuple[int, int]:
|
||||
lat_delta, long_delta = waypoint_delta
|
||||
return lat_delta * times, long_delta * times
|
||||
|
||||
|
||||
def translate_movement(waypoint_delta:Tuple[int, int], instruction:Instruction) -> Tuple[Tuple[int, int], int, int]:
|
||||
# Returns the new current direction and the lat/long delta
|
||||
|
||||
lat_delta = 0
|
||||
long_delta = 0
|
||||
|
||||
if instruction.action in ["l", "r"]:
|
||||
waypoint_delta = rotate_waypoint(waypoint_delta, instruction.action, instruction.magnitude)
|
||||
elif instruction.action == "f":
|
||||
lat_delta, long_delta = move_to_waypoint(waypoint_delta, instruction.magnitude)
|
||||
elif instruction.action in ["n", "s", "e", "w"]:
|
||||
wp_lat_delta, wp_long_delta = calculate_direction_deltas(instruction.action, instruction.magnitude)
|
||||
wp_lat, wp_long = waypoint_delta
|
||||
waypoint_delta = (wp_lat + wp_lat_delta, wp_long + wp_long_delta)
|
||||
else:
|
||||
raise AssertionError(f"invalid action '{instruction.action}'")
|
||||
|
||||
return waypoint_delta, lat_delta, long_delta
|
||||
|
||||
|
||||
def partTwo(instr: str) -> int:
|
||||
input_list = parse(instr)
|
||||
|
||||
waypoint_delta = (1, 10)
|
||||
lat = 0 # north/south, pos/neg
|
||||
long = 0 # west/east, pos/neg
|
||||
|
||||
for instruction in input_list:
|
||||
waypoint_delta, lad, lod = translate_movement(waypoint_delta, instruction)
|
||||
lat += lad
|
||||
long += lod
|
||||
|
||||
# Calculate manhattan distance
|
||||
# If either value is negative, make it positive
|
||||
if lat < 0:
|
||||
lat = lat + -2*lat
|
||||
if long < 0:
|
||||
long = long + -2*long
|
||||
|
||||
return lat + long
|
Loading…
Add table
Add a link
Reference in a new issue