Skip to content

Commit

Permalink
day23: part2 find edges
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-ong committed Dec 23, 2023
1 parent 2d387ad commit 6c0337a
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 89 deletions.
94 changes: 12 additions & 82 deletions day23/day23.py
Original file line number Diff line number Diff line change
@@ -1,103 +1,33 @@
from queue import Queue
from typing import Optional

from day23.lib.classes import Maze, Path, Position
from day23.lib.classes import Maze, Path, Solver, Solver1
from day23.lib.classes2 import Solver2
from day23.lib.parsers import get_maze

INPUT = "day23/input.txt"
INPUT_SMALL = "day23/input-small.txt"


class Solver:
maze: Maze
handle_hills: bool

def __init__(self, maze: Maze, handle_hills: bool = True) -> None:
self.maze = maze
self.handle_hills = handle_hills

def solve(self) -> list[Path]:
paths: Queue[Path] = Queue()
first_path = Path()
first_path.add(Position(0, 1))
paths.put(first_path)
# bfs all paths simultaneously
results: list[Path] = []
while not paths.empty():
path = paths.get()
if path.last().row == self.maze.num_rows - 1:
results.append(path)
continue

for expansion in self.expand_path(path):
paths.put(expansion)
return results

def expand_hill(self, position: Position, tile: str) -> list[Position]:
if tile == "^":
return [position.copy_modify(row=-1)]
elif tile == "v":
return [position.copy_modify(row=+1)]
elif tile == "<":
return [position.copy_modify(col=-1)]
elif tile == ">":
return [position.copy_modify(col=+1)]
else:
return position.expand()

def expand_path(self, path: Path) -> list[Path]:
current_pos: Position = path.last()
current_tile: Optional[str] = self.maze[current_pos]
if current_tile is None:
raise ValueError("there's no shot we got a tile outside the maze")
expansions: list[Position]
if self.handle_hills:
expansions = self.expand_hill(current_pos, current_tile)
else:
expansions = current_pos.expand()

valid_expansions = []
for expansion in expansions:
expansion_tile = self.maze[expansion]
if (
path.can_add(expansion)
and expansion_tile is not None
and expansion_tile != "#"
):
valid_expansions.append(expansion)

if len(valid_expansions) == 0:
return []
elif len(valid_expansions) == 1:
path.add(valid_expansions[0])
return [path]
else:
result = []
for expansion in valid_expansions[1:]:
new_path = path.copy()
new_path.add(expansion)
result.append(new_path)
path.add(valid_expansions[0])
result.append(path)
return result
def part1(maze: Maze) -> int:
solver = Solver1(maze, True)
return run_solver(solver)


def part1(maze: Maze) -> int:
return solve(maze, True)
def part2(maze: Maze) -> int:
solver = Solver2(maze)
return run_solver(solver)


def solve(maze: Maze, handle_hills: bool) -> int:
solver = Solver(maze, handle_hills)
def run_solver(solver: Solver) -> int:
paths: list[Path] = solver.solve()
path_lengths = [len(path) for path in paths]
path_lengths.sort(reverse=True)
return path_lengths[0]


def main() -> None:
maze: Maze = get_maze(INPUT)
maze: Maze = get_maze(INPUT_SMALL)

print(part1(maze))
# print(part1(maze))
print(part2(maze))


if __name__ == "__main__":
Expand Down
106 changes: 103 additions & 3 deletions day23/lib/classes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from queue import Queue
from typing import Optional


Expand Down Expand Up @@ -55,6 +56,11 @@ def copy(self) -> "Path":
result.nodes = set(result.route)
return result

def flip(self) -> "Path":
result = self.copy()
result.route.reverse()
return result

def last(self) -> Position:
if len(self.route) == 0:
raise ValueError("Don't call last when i'm empty 4head")
Expand All @@ -74,17 +80,17 @@ def __len__(self) -> int:


class Maze:
grid: list[str] # 2d array of chars
grid: list[list[str]] # 2d array of chars
num_rows: int
num_cols: int

def __init__(self, data: list[str]) -> None:
def __init__(self, data: list[list[str]]) -> None:
self.grid = data
self.num_rows = len(data)
self.num_cols = len(data[0])

def __str__(self) -> str:
return "\n".join(row for row in self.grid)
return "\n".join("".join(col for col in row) for row in self.grid)

def __getitem__(self, position: Position) -> Optional[str]:
"""Get item via position. Returns None if out of bounds"""
Expand All @@ -94,6 +100,14 @@ def __getitem__(self, position: Position) -> Optional[str]:
return None
return self.grid[position.row][position.col]

def __setitem__(self, position: Position, value: str) -> None:
"""Get item via position. Returns None if out of bounds"""
if not isinstance(position, Position):
raise ValueError(f"position is not a Position, {type(position)}")
if self.is_oob(position):
raise ValueError("can't set outside our maze!")
self.grid[position.row][position.col] = value

def is_oob(self, position: Position) -> bool:
"""true if position is out of bounds"""
return (
Expand All @@ -102,3 +116,89 @@ def is_oob(self, position: Position) -> bool:
or position.col < 0
or position.col >= self.num_cols
)


class Solver:
def solve(self) -> list[Path]:
return []


class Solver1(Solver):
maze: Maze
handle_hills: bool

def __init__(self, maze: Maze, handle_hills: bool = True) -> None:
self.maze = maze
self.handle_hills = handle_hills

def solve(self) -> list[Path]:
paths: Queue[Path] = Queue()
first_path = Path()
first_path.add(Position(0, 1))
paths.put(first_path)
# bfs all paths simultaneously
results: list[Path] = []
count = 1
while not paths.empty():
path = paths.get()
if path.last().row == self.maze.num_rows - 1:
results.append(path)
continue

expansions = self.expand_path(path)
for expansion in expansions:
paths.put(expansion)
count += 1
if count % 1000 == 0:
print(count)

return results

def expand_hill(self, position: Position, tile: str) -> list[Position]:
if tile == "^":
return [position.copy_modify(row=-1)]
elif tile == "v":
return [position.copy_modify(row=+1)]
elif tile == "<":
return [position.copy_modify(col=-1)]
elif tile == ">":
return [position.copy_modify(col=+1)]
else:
return position.expand()

def expand_path(self, path: Path) -> list[Path]:
current_pos: Position = path.last()
current_tile: Optional[str] = self.maze[current_pos]
if current_tile is None:
raise ValueError("there's no shot we got a tile outside the maze")
expansions: list[Position]
if self.handle_hills:
expansions = self.expand_hill(current_pos, current_tile)
else:
expansions = current_pos.expand()

valid_expansions = []
for expansion in expansions:
expansion_tile = self.maze[expansion]
if (
path.can_add(expansion)
and expansion_tile is not None
and expansion_tile != "#"
):
valid_expansions.append(expansion)

if len(valid_expansions) == 0:
return []
elif len(valid_expansions) == 1:
path.add(valid_expansions[0])
return [path]
else:
result = []
for expansion in valid_expansions[1:]:
new_path = path.copy()
new_path.add(expansion)
result.append(new_path)
path.add(valid_expansions[0])
result.append(path)

return result
135 changes: 135 additions & 0 deletions day23/lib/classes2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""part 2 solution"""
from dataclasses import dataclass, field
from queue import Queue

import colorama

from day23.lib.classes import Maze, Path, Position, Solver


# first simplify graph to "nodes", then brute force that.
@dataclass
class Node:
name: int
position: Position
edges: list["Edge"] = field(default_factory=list)

def __str__(self) -> str:
return f"{self.name}: ({self.position}) {[str(edge) for edge in self.edges]}"


@dataclass
class Edge:
node1: int
node2: int
path: Path = field(repr=False)

def flip(self) -> "Edge":
return Edge(self.node2, self.node1, self.path.flip())

def __str__(self) -> str:
return f"{self.node1}->{self.node2}, {len(self.path)}"


class Solver2(Solver):
maze: Maze

def __init__(self, maze: Maze) -> None:
self.maze = maze

def get_cell_branches(self, position: Position) -> int:
result = 0
if self.maze[position] != ".":
return 0
for direction in position.expand():
tile = self.maze[direction]
if tile is not None and tile != "#":
result += 1
return result

def get_nodes(self) -> dict[Position, Node]:
nodes: list[Node] = []

start = Position(0, 1)
nodes.append(Node(0, start))
name = 1
for row in range(self.maze.num_rows):
for col in range(self.maze.num_cols):
pos = Position(row, col)
if self.get_cell_branches(pos) > 2:
node = Node(name, pos)
name += 1
nodes.append(node)

# add start and end coz they are dumb

end = Position(self.maze.num_rows - 1, self.maze.num_cols - 2)

nodes.append(Node(name, end))
for node in nodes:
self.maze[node.position] = (
colorama.Back.GREEN + str(node.name) + colorama.Back.BLACK
)
return {node.position: node for node in nodes}

def fill_node(self, start_node: Node, nodes: dict[Position, Node]) -> None:
first_path = Path()
first_path.add(start_node.position)
paths: Queue[Path] = Queue()
paths.put(first_path)
while not paths.empty():
path = paths.get()
pos = path.last()
if pos != start_node.position and pos in nodes:
# reached an edge
edge = Edge(start_node.name, nodes[pos].name, path)
start_node.edges.append(edge)
end_node = nodes[pos]
end_node.edges.append(edge.flip())
continue
expansions = self.expand_path(path)
for path in expansions:
paths.put(path)

def expand_path(self, path: Path) -> list[Path]:
current_pos: Position = path.last()
expansions = current_pos.expand()

valid_expansions = []
for expansion in expansions:
expansion_tile = self.maze[expansion]
if (
path.can_add(expansion)
and expansion_tile is not None
and expansion_tile != "#"
):
valid_expansions.append(expansion)
if expansion_tile == ".":
self.maze[expansion] = "#"

if len(valid_expansions) == 0:
return []
elif len(valid_expansions) == 1:
path.add(valid_expansions[0])
return [path]
else:
result = []
for expansion in valid_expansions[1:]:
new_path = path.copy()
new_path.add(expansion)
result.append(new_path)
path.add(valid_expansions[0])
result.append(path)

return result

def solve(self) -> list[Path]:
nodes: dict[Position, Node] = self.get_nodes()
print(self.maze)
for node in nodes.values():
self.fill_node(node, nodes)

for node in nodes.values():
print(node)

return []
Loading

0 comments on commit 6c0337a

Please sign in to comment.