InternBootcamp/internbootcamp/libs/campsite/campsite_solver.py
2025-05-23 15:27:15 +08:00

140 lines
5.8 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

def solve_campsite(grid, row_constraints, col_constraints):
"""
Solve the 'Campsite' puzzle using backtracking.
:param grid: A list of lists of characters ('T' or 'X') of size n×m.
:param row_constraints: A list of length n with the required tent count per row.
:param col_constraints: A list of length m with the required tent count per column.
:return: A list of lists representing a solution (replacing 'X' with either 'C' or 'X'),
or None if no solution exists.
"""
n = len(grid)
m = len(grid[0])
# Copy the grid to store the solution without mutating the original
solution = [row[:] for row in grid]
# Track how many tents are currently placed in each row/column
current_row_tents = [0] * n
current_col_tents = [0] * m
# Precompute which cells are adjacent to at least one tree
# because a tent can only go orthogonally adjacent to a tree
adjacent_to_tree = [[False]*m for _ in range(n)]
directions_orth = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for r in range(n):
for c in range(m):
if grid[r][c] == 'T':
for dr, dc in directions_orth:
rr, cc = r+dr, c+dc
if 0 <= rr < n and 0 <= cc < m and grid[rr][cc] == 'X':
adjacent_to_tree[rr][cc] = True
# Helper function to check if placing a tent at (r, c) violates adjacency constraints
def can_place_tent(r, c):
# If the cell is not empty or not adjacent to a tree, cannot place a tent
if solution[r][c] != 'X' or not adjacent_to_tree[r][c]:
return False
# Check row/col constraints if adding one more tent is still valid
if current_row_tents[r] + 1 > row_constraints[r]:
return False
if current_col_tents[c] + 1 > col_constraints[c]:
return False
# Tents cannot be adjacent (orth or diag) to any existing tent
# We'll check the 8 directions around (r, c)
directions_all = [
(-1, 0), (1, 0), (0, -1), (0, 1),
(-1, -1), (-1, 1), (1, -1), (1, 1)
]
for dr, dc in directions_all:
rr, cc = r+dr, c+dc
if 0 <= rr < n and 0 <= cc < m and solution[rr][cc] == 'C':
return False
return True
# A list of all grid coordinates we might try to fill
# Sorting them can sometimes help performance, but plain is fine, too.
all_cells = [(r, c) for r in range(n) for c in range(m)]
history = []
# We'll do a simple backtracking approach that tries to place a tent or not
def backtrack(idx=0):
if idx == len(all_cells):
history.append(f"All positions have been tried. Check if the current solution satisfies the row constraints and the col constraints.\n")
is_satisfy = all(current_row_tents[r] == row_constraints[r] for r in range(n)) and all(current_col_tents[c] == col_constraints[c] for c in range(m))
if is_satisfy:
history.append("The current solution satisfy all the conditions. Find the solution!\n")
else:
history.append("The current solution does not satisfy all the conditions.\n")
return is_satisfy
r, c = all_cells[idx]
history.append(f"Select ({r},{c}), check if the tent can be placed in this location.\n")
# -----------------------------------------------------------
# OPTION 1: Place a tent in (r, c) if it's a valid move
# -----------------------------------------------------------
if can_place_tent(r, c):
history.append(f"Tent can be placed at ({r},{c}). Place a tent at ({r},{c}).\n")
solution[r][c] = 'C'
current_row_tents[r] += 1
current_col_tents[c] += 1
if backtrack(idx + 1):
return True
history.append(f"Since current solution fails. Change the current location ({r},{c}) from a tent(C) to an open space(X). Then retry.\n")
# Backtrack (undo placement)
solution[r][c] = 'X'
current_row_tents[r] -= 1
current_col_tents[c] -= 1
else:
history.append(f"No tents can be placed in ({r},{c}).\n")
# -----------------------------------------------------------
# OPTION 2: Skip placing a tent at (r, c)
# -----------------------------------------------------------
if backtrack(idx + 1):
return True
history.append(f"Since there is no tent placed at the current location ({r},{c}), we backtrack to the previous location.\n")
# If neither placing nor skipping yields a solution, fail here
return False
# Kick off the backtracking
if backtrack():
history.append('\nThe final answer is: '+'{'+'\n'.join([' '.join(row) for row in solution]) + '}')
steps = ''.join(history)
return solution, steps
else:
return None
# -----------------------------------------------------------------
# Example usage:
#
# Suppose you have the puzzle:
if __name__ == "__main__":
grid = [
['X','X','X','X','X','T','X','X','T','X'],
['X','T','X','X','X','X','X','X','X','X'],
['X','X','X','T','T','X','X','X','X','X'],
['T','X','X','X','X','X','T','T','X','X'],
['X','T','X','X','T','X','X','X','X','X']
]
row_constraints = [3, 1, 3, 0, 3]
col_constraints = [2, 1, 1, 1, 1, 0, 2, 1, 0, 1]
solved, steps = solve_campsite(grid, row_constraints, col_constraints)
if solved:
# Format the output as requested
print(steps)
else:
print("No solution found (but puzzle is assumed to have a unique solution).")
#
# You can adapt this to parse your input format, then call `solve_campsite`
# to get the solution, and finally output the result in the requested
# "[[row1, row2, ...]]" string format.