Day 12 (Python)

This commit is contained in:
akp 2020-12-12 14:11:54 +00:00
parent f5afabaadf
commit bdba0ff639
No known key found for this signature in database
GPG key ID: D3E7EAA31B39637E
7 changed files with 339 additions and 1 deletions

2
.github/README.md vendored
View file

@ -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
View 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
View 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
}
]
}
}

View 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))

View 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

View 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

View 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