Day 24 (Python)
This commit is contained in:
parent
f203d259e0
commit
8a75eaacea
7 changed files with 326 additions and 1 deletions
2
.github/README.md
vendored
2
.github/README.md
vendored
|
@ -39,7 +39,7 @@ Puzzle inputs and descriptions are not included in this repository. You'll have
|
|||
| [21](/21-allergenAmusement) | ![Partially complete][partial] | [Link](/21-allergenAmusement/python) | | |
|
||||
| [22](/22-crabCombat) | ![Partially complete][partial] | [Link](/22-crabCombat/python) | | |
|
||||
| [23](/23-crabCups) | ![Incomplete][cross] | | | |
|
||||
| 24 | ![Not yet attempted][pending] | | | |
|
||||
| [24](/24-lobbyLayout) | ![Partially complete][partial] | [Link](/24-lobbyLayout/python) | | |
|
||||
| 25 | | | | |
|
||||
|
||||
<!-- PARSE END -->
|
||||
|
|
58
24-lobbyLayout/README.md
Normal file
58
24-lobbyLayout/README.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# [Day 24: Lobby Layout](https://adventofcode.com/2020/day/24)
|
||||
|
||||
More cellular automata! Just this time, weird. Perhaps even weirder than [day 17](../17-conwayCubes). Hexagonal cells broke my brain a pretty substantial amount.
|
||||
|
||||
[This StackOverflow answer](https://stackoverflow.com/a/7393897) proved pretty crucial to me ending up solving the challenge. To summarise:
|
||||
|
||||
We can represent our flooring using a typical 2D array, provided we visualise it with each line offset by a constant amount from the previous like so:
|
||||
|
||||
```
|
||||
(0,0) (0,1) (0,2) (0,3) (0,4)
|
||||
|
||||
(1,0) (1,1) (1,2) (1,3) (1,4)
|
||||
|
||||
(2,0) (2,1) (2,2) (2,3) (2,4)
|
||||
|
||||
(3,0) (3,1) (3,2) (3,3) (3,4)
|
||||
```
|
||||
|
||||
The six neighbours to a given cell form what I believe called a slanted neighbourhood.
|
||||
|
||||
Using this, we can find a series of vectors that represent `e`, `se`, `sw`, `w`, `nw`, and `ne` directions from a specific tile.
|
||||
|
||||
```
|
||||
(r, s) current tile
|
||||
|
||||
(r-1, s) nw
|
||||
(r-1, s+1) ne
|
||||
(r, s-1) w
|
||||
(r, s+1) e
|
||||
(r+1, s-1) sw
|
||||
(r+1, s) se
|
||||
```
|
||||
|
||||
From here onwards, it's pretty simple to solve the rest of the challenge.
|
||||
|
||||
### Related
|
||||
|
||||
* [Wikipedia - Cellular automata](https://en.wikipedia.org/wiki/Cellular_automaton)
|
||||
* [Tim Hutton - A Slanted Hexagon](https://ferkeltongs.livejournal.com/31455.html)
|
||||
* [StackOverflow - What's the best way to represent a hexagonal lattice?](https://stackoverflow.com/a/7393897)
|
||||
|
||||
<details><summary>Script output</summary>
|
||||
|
||||
```
|
||||
❯ python .\python\ ft
|
||||
AoC 2020: day 24 - Lobby Layout
|
||||
Python 3.8.5
|
||||
|
||||
Test cases
|
||||
1.1 pass in 0.0004611015319824219 seconds
|
||||
2.1 pass in 0.6723911762237549 seconds
|
||||
|
||||
Answers
|
||||
Part 1: 497 in 0.004002571105957031 seconds
|
||||
Part 2: 4156 in 1.6635417938232422 seconds
|
||||
```
|
||||
|
||||
</details>
|
19
24-lobbyLayout/info.json
Normal file
19
24-lobbyLayout/info.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"year": "2020",
|
||||
"day": "24",
|
||||
"title": "Lobby Layout",
|
||||
"testCases": {
|
||||
"one": [
|
||||
{
|
||||
"input": "sesenwnenenewseeswwswswwnenewsewsw\nneeenesenwnwwswnenewnwwsewnenwseswesw\nseswneswswsenwwnwse\nnwnwneseeswswnenewneswwnewseswneseene\nswweswneswnenwsewnwneneseenw\neesenwseswswnenwswnwnwsewwnwsene\nsewnenenenesenwsewnenwwwse\nwenwwweseeeweswwwnwwe\nwsweesenenewnwwnwsenewsenwwsesesenwne\nneeswseenwwswnwswswnw\nnenwswwsewswnenenewsenwsenwnesesenew\nenewnwewneswsewnwswenweswnenwsenwsw\nsweneswneswneneenwnewenewwneswswnese\nswwesenesewenwneswnwwneseswwne\nenesenwswwswneneswsenwnewswseenwsese\nwnwnesenesenenwwnenwsewesewsesesew\nnenewswnwewswnenesenwnesewesw\neneswnwswnwsenenwnwnwwseeswneewsenese\nneswnwewnwnwseenwseesewsenwsweewe\nwseweeenwnesenwwwswnew\n",
|
||||
"expected": 10
|
||||
}
|
||||
],
|
||||
"two": [
|
||||
{
|
||||
"input": "sesenwnenenewseeswwswswwnenewsewsw\nneeenesenwnwwswnenewnwwsewnenwseswesw\nseswneswswsenwwnwse\nnwnwneseeswswnenewneswwnewseswneseene\nswweswneswnenwsewnwneneseenw\neesenwseswswnenwswnwnwsewwnwsene\nsewnenenenesenwsewnenwwwse\nwenwwweseeeweswwwnwwe\nwsweesenenewnwwnwsenewsenwwsesesenwne\nneeswseenwwswnwswswnw\nnenwswwsewswnenenewsenwsenwnesesenew\nenewnwewneswsewnwswenweswnenwsenwsw\nsweneswneswneneenwnewenewwneswswnese\nswwesenesewenwneswnwwneseswwne\nenesenwswwswneneswsenwnewswseenwsese\nwnwnesenesenenwwnenwsewesewsesesew\nnenewswnwewswnenesenwnesewesw\neneswnwswnwsenenwnwnwwseeswneewsenese\nneswnwewnwnwseenwseesewsenwsweewe\nwseweeenwnesenwwwswnew\n",
|
||||
"expected": 2208
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
118
24-lobbyLayout/python/__main__.py
Normal file
118
24-lobbyLayout/python/__main__.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import json
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
|
||||
from rich import print
|
||||
|
||||
from partOne import partOne
|
||||
from partTwo import partTwo
|
||||
|
||||
|
||||
force_time = False
|
||||
|
||||
|
||||
def run_and_time(f):
|
||||
st = time.time()
|
||||
x = f()
|
||||
et = time.time()
|
||||
return x, et - st
|
||||
|
||||
|
||||
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):
|
||||
readable_test_num = f"{n}.{i+1}"
|
||||
print(f"Running {readable_test_num}...", end="\r")
|
||||
|
||||
expectedInt = tc["expected"]
|
||||
|
||||
result, t = run_and_time(lambda: f(str(tc["input"])))
|
||||
|
||||
output_string = f"{readable_test_num} "
|
||||
|
||||
if result == expectedInt:
|
||||
output_string += "[green]pass[/green]"
|
||||
else:
|
||||
output_string += (
|
||||
f"[red]fail[/red] (got {result}, expected {expectedInt})"
|
||||
)
|
||||
|
||||
if t > 15 or force_time:
|
||||
output_string += f" in {t} seconds"
|
||||
|
||||
print(output_string + " " * 12)
|
||||
|
||||
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()
|
||||
|
||||
if "ft" in sys.argv:
|
||||
force_time = True
|
||||
|
||||
run_tests(info["testCases"])
|
||||
|
||||
if "debug" in sys.argv:
|
||||
sys.exit()
|
||||
|
||||
print("Answers")
|
||||
|
||||
print("Running part 1...", end="\r")
|
||||
output_string = "Part 1: "
|
||||
x, t = run_and_time(lambda: partOne(challenge_input))
|
||||
output_string += str(x)
|
||||
if t > 15 or force_time:
|
||||
output_string += f" in {t} seconds"
|
||||
print(output_string + " " * 12)
|
||||
|
||||
print("Running part 2...", end="\r")
|
||||
output_string = "Part 2: "
|
||||
x, t = run_and_time(lambda: partTwo(challenge_input))
|
||||
output_string += str(x)
|
||||
if t > 15 or force_time:
|
||||
output_string += f" in {t} seconds"
|
||||
print(output_string + " " * 12)
|
62
24-lobbyLayout/python/common.py
Normal file
62
24-lobbyLayout/python/common.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
from typing import List, Tuple, Dict
|
||||
|
||||
|
||||
def make_vector(loc: List[str], start: Tuple[int, int] = (0, 0)) -> Tuple[int, int]:
|
||||
r, s = start
|
||||
|
||||
for ins in loc:
|
||||
if ins == "nw":
|
||||
r -= 1
|
||||
elif ins == "ne":
|
||||
r -= 1
|
||||
s += 1
|
||||
elif ins == "w":
|
||||
s -= 1
|
||||
elif ins == "e":
|
||||
s += 1
|
||||
elif ins == "sw":
|
||||
r += 1
|
||||
s -= 1
|
||||
elif ins == "se":
|
||||
r += 1
|
||||
|
||||
return r, s
|
||||
|
||||
|
||||
def make_initial_state(tile_locations: List[List[str]]) -> Dict[Tuple[int, int], bool]:
|
||||
# True represents a black tile - false is white
|
||||
tiles = {}
|
||||
for loc in tile_locations:
|
||||
pos = make_vector(loc)
|
||||
tiles[pos] = not tiles.get(pos, False)
|
||||
return tiles
|
||||
|
||||
|
||||
def count_black_tiles(tiles: Dict[Tuple[int, int], bool]) -> int:
|
||||
black_tiles = 0
|
||||
for key in tiles:
|
||||
if tiles[key]:
|
||||
black_tiles += 1
|
||||
return black_tiles
|
||||
|
||||
|
||||
def parse(instr: str) -> List[str]:
|
||||
# e, se, sw, w, nw, and ne
|
||||
|
||||
tiles = instr.strip().split("\n")
|
||||
o = []
|
||||
|
||||
for tile_line in tiles:
|
||||
tl = []
|
||||
pointer = 0
|
||||
while pointer < len(tile_line):
|
||||
current = tile_line[pointer]
|
||||
if current in ["s", "n"]:
|
||||
tl.append(tile_line[pointer : pointer + 2])
|
||||
pointer += 2
|
||||
else:
|
||||
tl.append(current)
|
||||
pointer += 1
|
||||
o.append(tl)
|
||||
|
||||
return o
|
7
24-lobbyLayout/python/partOne.py
Normal file
7
24-lobbyLayout/python/partOne.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
from common import *
|
||||
|
||||
|
||||
def partOne(instr: str) -> int:
|
||||
tiles_locations = parse(instr)
|
||||
tiles = make_initial_state(tiles_locations)
|
||||
return count_black_tiles(tiles)
|
61
24-lobbyLayout/python/partTwo.py
Normal file
61
24-lobbyLayout/python/partTwo.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from common import *
|
||||
|
||||
|
||||
offsets = (
|
||||
(-1, 0),
|
||||
(-1, +1),
|
||||
(0, -1),
|
||||
(0, +1),
|
||||
(+1, -1),
|
||||
(+1, 0),
|
||||
)
|
||||
|
||||
|
||||
def count_neighbours(tiles: Dict[Tuple[int, int], bool], pos: Tuple[int, int]) -> int:
|
||||
count = 0
|
||||
r, s = pos
|
||||
for ro, so in offsets:
|
||||
if tiles.get((r + ro, s + so), False):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def iterate(tiles: Dict[Tuple[int, int], bool]) -> None:
|
||||
changes = {}
|
||||
|
||||
min_r, _ = min(tiles, key=lambda x: x[0])
|
||||
max_r, _ = max(tiles, key=lambda x: x[0])
|
||||
_, min_s = min(tiles, key=lambda x: x[1])
|
||||
_, max_s = max(tiles, key=lambda x: x[1])
|
||||
|
||||
for r in range(min_r - 2, max_r + 2):
|
||||
for s in range(min_s - 2, max_s + 2):
|
||||
neighbours = count_neighbours(tiles, (r, s))
|
||||
current_state = tiles.get((r, s), False) # true is black, false is white
|
||||
|
||||
# black tile with zero or more than 2 black tiles adjacent is flipped to white.
|
||||
if current_state and (neighbours == 0 or neighbours > 2):
|
||||
changes[(r, s)] = False
|
||||
# white tile with exactly 2 black tiles adjacent is flipped to black.
|
||||
elif not current_state and neighbours == 2:
|
||||
changes[(r, s)] = True
|
||||
|
||||
# apply changes to master dictionary
|
||||
for pos in changes:
|
||||
ns = changes[pos]
|
||||
if changes[pos]:
|
||||
tiles[pos] = ns
|
||||
else:
|
||||
del tiles[pos]
|
||||
|
||||
return
|
||||
|
||||
|
||||
def partTwo(instr: str) -> int:
|
||||
tiles_locations = parse(instr)
|
||||
tiles = make_initial_state(tiles_locations)
|
||||
|
||||
for _ in range(100):
|
||||
iterate(tiles)
|
||||
|
||||
return count_black_tiles(tiles)
|
Loading…
Add table
Add a link
Reference in a new issue