improve Day10 in python

This commit is contained in:
WieErWill 2023-12-11 09:42:32 +01:00
parent a5dca38d0e
commit 1d298812da
4 changed files with 267 additions and 102 deletions

View File

@ -101,3 +101,102 @@ Here are the distances for each tile on that loop:
23...
```
Find the single giant loop starting at S. How many steps along the loop does it take to get from the starting position to the point farthest from the starting position?
# Part Two
You quickly reach the farthest point of the loop, but the animal never emerges. Maybe its nest is within the area enclosed by the loop?
To determine whether it's even worth taking the time to search for such a nest, you should calculate how many tiles are contained within the loop. For example:
```
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........
```
The above loop encloses merely four tiles - the two pairs of . in the southwest and southeast (marked I below). The middle . tiles (marked O below) are not in the loop. Here is the same loop again with those regions marked:
```
...........
.S-------7.
.|F-----7|.
.||OOOOO||.
.||OOOOO||.
.|L-7OF-J|.
.|II|O|II|.
.L--JOL--J.
.....O.....
```
In fact, there doesn't even need to be a full tile path to the outside for tiles to count as outside the loop - squeezing between pipes is also allowed! Here, I is still within the loop and O is still outside the loop:
```
..........
.S------7.
.|F----7|.
.||OOOO||.
.||OOOO||.
.|L-7F-J|.
.|II||II|.
.L--JL--J.
..........
```
In both of the above examples, 4 tiles are enclosed by the loop.
Here's a larger example:
```
.F----7F7F7F7F-7....
.|F--7||||||||FJ....
.||.FJ||||||||L7....
FJL7L7LJLJ||LJ.L-7..
L--J.L7...LJS7F-7L7.
....F-J..F7FJ|L7L7L7
....L7.F7||L7|.L7L7|
.....|FJLJ|FJ|F7|.LJ
....FJL-7.||.||||...
....L---J.LJ.LJLJ...
```
The above sketch has many random bits of ground, some of which are in the loop (I) and some of which are outside it (O):
```
OF----7F7F7F7F-7OOOO
O|F--7||||||||FJOOOO
O||OFJ||||||||L7OOOO
FJL7L7LJLJ||LJIL-7OO
L--JOL7IIILJS7F-7L7O
OOOOF-JIIF7FJ|L7L7L7
OOOOL7IF7||L7|IL7L7|
OOOOO|FJLJ|FJ|F7|OLJ
OOOOFJL-7O||O||||OOO
OOOOL---JOLJOLJLJOOO
```
In this larger example, 8 tiles are enclosed by the loop.
Any tile that isn't part of the main loop can count as being enclosed by the loop. Here's another example with many bits of junk pipe lying around that aren't connected to the main loop at all:
```
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
```
Here are just the tiles that are enclosed by the loop marked with I:
```
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJIF7FJ-
L---JF-JLJIIIIFJLJJ7
|F|F-JF---7IIIL7L|7|
|FFJF7L7F-JF7IIL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
```
In this last example, 10 tiles are enclosed by the loop.
Figure out whether you have time to search for the nest by calculating the area within the loop. How many tiles are enclosed by the loop?

View File

@ -1,114 +1,105 @@
def parse_grid(file_path):
"""Parses the grid from a file and returns it as a 2D list."""
"""Parses the grid from a file and returns the graph and starting position."""
print(f"Parsing grid from file: {file_path}")
grid = {}
start = None
try:
with open(file_path, "r") as file:
grid = [list(line.strip()) for line in file]
print(f"Grid parsed from {file_path}:")
[print("".join(row)) for row in grid]
return grid
with open(file_path, 'r') as file:
for y, line in enumerate(file.read().splitlines()):
for x, char in enumerate(line):
match char:
case "|":
grid[(y, x)] = {(y - 1, x), (y + 1, x)}
case "-":
grid[(y, x)] = {(y, x - 1), (y, x + 1)}
case "L":
grid[(y, x)] = {(y - 1, x), (y, x + 1)}
case "J":
grid[(y, x)] = {(y, x - 1), (y - 1, x)}
case "7":
grid[(y, x)] = {(y, x - 1), (y + 1, x)}
case "F":
grid[(y, x)] = {(y + 1, x), (y, x + 1)}
case "S":
start = (y, x)
grid[(y, x)] = {(y, x - 1), (y, x + 1), (y - 1, x), (y + 1, x)}
case _:
pass
if start is None:
raise ValueError("Start position 'S' not found in the grid.")
grid[start] = {dst for dst in grid[start] if start in grid.get(dst, set())}
return grid, start
except FileNotFoundError:
print(f"File not found: {file_path}")
raise
except Exception as e:
print(f"Error reading file {file_path}: {e}")
print(f"Error parsing grid: {str(e)}")
raise
def create_graph(grid):
"""Creates a graph from the grid."""
graph = {}
rows, cols = len(grid), len(grid[0])
for r in range(rows):
for c in range(cols):
if grid[r][c] in "|-LJ7FS":
graph[(r, c)] = get_neighbors(grid, r, c)
print("Graph created from grid:")
print(graph)
return graph
def get_neighbors(grid, r, c):
"""Finds the neighbors of a cell in the grid."""
neighbors = []
rows, cols = len(grid), len(grid[0])
# Directions: North, East, South, West
directions = [(r - 1, c), (r, c + 1), (r + 1, c), (r, c - 1)]
connected = {
"|": [0, 2],
"-": [1, 3],
"L": [0, 1],
"J": [0, 3],
"7": [2, 3],
"F": [1, 2],
"S": [0, 1, 2, 3], # 'S' connects in all directions for initial identification
}
for i, (dr, dc) in enumerate(directions):
if 0 <= dr < rows and 0 <= dc < cols and grid[dr][dc] != ".":
neighbor_type = grid[dr][dc]
# Check if there is a valid connection
if neighbor_type in connected:
if (
i in connected[grid[r][c]] and (3 - i) in connected[neighbor_type]
): # Check reverse direction
neighbors.append((dr, dc))
print(f"Neighbors for ({r}, {c}): {neighbors}")
return neighbors
def bfs(graph, start):
"""Performs BFS on the graph and returns the maximum distance from the start."""
visited = set()
queue = [(start, 0)]
max_distance = 0
def find_cycle(grid, start):
"""Finds all nodes in the cycle starting from the starting position."""
print(f"Finding cycle from start position: {start}")
seen = {start}
queue = [start]
while queue:
node, distance = queue.pop(0)
if node != start or len(visited) == 0: # Allow revisiting start only initially
visited.add(node)
current = queue.pop(0)
for dst in grid[current]:
if dst not in seen:
seen.add(dst)
queue.append(dst)
return seen
def bfs_distance(grid, start, target):
"""Performs BFS to find distance from start to target."""
#print(f"Calculating BFS distance from {start} to {target}")
queue = [(start, 0)]
visited = set()
while queue:
current, distance = queue.pop(0)
if current == target:
return distance
visited.add(current)
for neighbor in grid[current]:
if neighbor not in visited:
queue.append((neighbor, distance + 1))
return -1
def find_longest_distance(grid, cycle, start):
"""Finds the longest distance from the start in the cycle."""
print("Finding longest distance from start in the cycle.")
max_distance = 0
for node in cycle:
if node != start:
distance = bfs_distance(grid, start, node)
max_distance = max(max_distance, distance)
print(f"Visited node: {node}, Distance: {distance}")
for neighbor in graph[node]:
if neighbor not in visited or (neighbor == start and len(visited) > 1):
queue.append((neighbor, distance + 1))
print(f"Maximum distance from start: {max_distance}")
return max_distance
def run(file_path):
"""Runs the entire algorithm for the given file path."""
print(f"Running algorithm for file: {file_path}")
grid, start = parse_grid(file_path)
cycle = find_cycle(grid, start)
longest_distance = find_longest_distance(grid, cycle, start)
print(f"Longest distance: {longest_distance}")
return longest_distance
def find_start(grid):
"""Finds the starting position 'S' in the grid."""
for r, row in enumerate(grid):
for c, cell in enumerate(row):
if cell == "S":
print(f"Starting position found at: ({r}, {c})")
return r, c
raise ValueError("Starting position 'S' not found in the grid")
def run_test(file_path):
"""Runs the algorithm on a test file and asserts the result."""
print(f"Running test with file: {file_path}")
grid = parse_grid(file_path)
graph = create_graph(grid)
start = find_start(grid)
max_distance = bfs(graph, start)
print(f"Max distance for test: {max_distance}")
return max_distance
def main(file_path):
"""Main function to run the algorithm on the input file."""
print(f"Running main algorithm with file: {file_path}")
grid = parse_grid(file_path)
graph = create_graph(grid)
start = find_start(grid)
max_distance = bfs(graph, start)
print(f"Max distance for input: {max_distance}")
return max_distance
def test():
"""Test function to validate the algorithm with a test file."""
print("Starting test...")
expected_result = 8 # Expected result for the test file
result = run("../test.txt")
assert result == expected_result, f"Test failed: Expected {expected_result}, got {result}"
print("Test passed successfully.")
def main():
"""Main function to run the test and then the algorithm for the input file."""
try:
test()
final_result = run("../input.txt")
print(f"Final Result: {final_result}")
except Exception as e:
print(f"An error occurred: {str(e)}")
if __name__ == "__main__":
test_result = run_test("../test.txt")
assert test_result == 8, f"Test failed: expected 8, got {test_result}"
print(f"Test passed with {test_result}")
input_result = main("../input.txt")
print(f"Result for input file: {input_result}")
main()

66
Day10/python/solution2.py Normal file
View File

@ -0,0 +1,66 @@
def parse_grid(data):
grid = {}
for y, line in enumerate(data):
for x, char in enumerate(line):
match char:
case "|":
grid[(y, x)] = {(y - 1, x), (y + 1, x)}
case "-":
grid[(y, x)] = {(y, x - 1), (y, x + 1)}
case "L":
grid[(y, x)] = {(y - 1, x), (y, x + 1)}
case "J":
grid[(y, x)] = {(y, x - 1), (y - 1, x)}
case "7":
grid[(y, x)] = {(y, x - 1), (y + 1, x)}
case "F":
grid[(y, x)] = {(y + 1, x), (y, x + 1)}
case "S":
start = (y, x)
grid[(y, x)] = {
(y, x - 1),
(y, x + 1),
(y - 1, x),
(y + 1, x),
}
case _:
pass
grid[start] = {dst for dst in grid[start] if start in grid.get(dst, set())}
return grid, start
def find_cycle(grid, start):
seen = {start}
queue = [start]
while queue:
current = queue.pop(0)
for dst in grid[current]:
if dst not in seen:
seen.add(dst)
queue.append(dst)
return seen
def find_inside(grid, cycle):
max_y = max(y for y, _ in grid.keys())
max_x = max(x for _, x in grid.keys())
inside = set()
for y in range(max_y):
pipe = 0
for x in range(max_x):
pos = (y, x)
if pos in cycle:
if (y - 1, x) in grid[pos]:
pipe += 1
elif pipe % 2 == 1:
inside.add(pos)
return len(inside)
input_grid, input_start = parse_grid(open("../input.txt").read().splitlines())
print(len(find_cycle(input_grid, input_start)) // 2)
print(find_inside(input_grid, find_cycle(input_grid, input_start)))

9
Day10/test2.txt Normal file
View File

@ -0,0 +1,9 @@
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........