135 lines
5.0 KiB
Python
135 lines
5.0 KiB
Python
import sys
|
|
from typing import Iterable, Iterator, TYPE_CHECKING
|
|
|
|
|
|
class State(int):
|
|
def get_digit(self, idx): # 0 开始
|
|
return self // (10 ** idx) % 10
|
|
|
|
def with_digit(self, idx: int, val: int) -> "State":
|
|
return State(self + (val - self.get_digit(idx)) * 10 ** idx)
|
|
|
|
def get_coord_digit(self, row: int, col: int):
|
|
idx = 3 * (3 - row) - (col + 1)
|
|
return self.get_digit(idx)
|
|
|
|
def with_coord_digit(self, row: int, col: int, val: int) -> "State":
|
|
idx = 3 * (3 - row) - (col + 1)
|
|
return self.with_digit(idx, val)
|
|
|
|
def get_number_coord(self, n: int = 0) -> tuple[int, int]:
|
|
for r in range(3):
|
|
for c in range(3):
|
|
if self.get_coord_digit(r, c) == n:
|
|
return r, c
|
|
raise ValueError(f"No {n} in state {self}")
|
|
|
|
def get_possible_steps(self) -> Iterable["State"]:
|
|
x0, y0 = self.get_number_coord()
|
|
if x0 > 0:
|
|
yield self.with_coord_digit(x0, y0, self.get_coord_digit(x0 - 1, y0)) \
|
|
.with_coord_digit(x0 - 1, y0, 0)
|
|
if y0 > 0:
|
|
yield self.with_coord_digit(x0, y0, self.get_coord_digit(x0, y0 - 1)) \
|
|
.with_coord_digit(x0, y0 - 1, 0)
|
|
if x0 < 2:
|
|
yield self.with_coord_digit(x0, y0, self.get_coord_digit(x0 + 1, y0)) \
|
|
.with_coord_digit(x0 + 1, y0, 0)
|
|
if y0 < 2:
|
|
yield self.with_coord_digit(x0, y0, self.get_coord_digit(x0, y0 + 1)) \
|
|
.with_coord_digit(x0, y0 + 1, 0)
|
|
|
|
def compare_with(self, other_state: "State") -> int:
|
|
# return 0
|
|
# return 9 - sum(self.get_digit(idx) == other_state.get_digit(idx) for idx in range(9))
|
|
# return max(0, 8 - sum(self.get_digit(idx) == other_state.get_digit(idx) for idx in range(9)))
|
|
d1 = sum(self.get_digit(idx) != other_state.get_digit(idx) for idx in range(9))
|
|
if self.get_number_coord() != other_state.get_number_coord():
|
|
d1 -= 1
|
|
d2 = self.manhattan_distance(other_state)
|
|
if d1 > d2: print("?????????", self, other_state, d1, d2)
|
|
return d1
|
|
|
|
def manhattan_distance(self, other_state: "State") -> int:
|
|
number_coords = {}
|
|
other_number_coords = {}
|
|
for r in range(3):
|
|
for c in range(3):
|
|
number_coords[self.get_coord_digit(r, c)] = (r, c)
|
|
other_number_coords[other_state.get_coord_digit(r, c)] = (r, c)
|
|
|
|
# del number_coords[0]
|
|
# del other_number_coords[0]
|
|
return sum(
|
|
abs(number_coords[i][0] - other_number_coords[i][0]) + abs(number_coords[i][1] - other_number_coords[i][1])
|
|
for i in range(1, 9))
|
|
|
|
|
|
class StateNode:
|
|
def __init__(self,
|
|
state: State | int | str, cost: int,
|
|
heuristic_cost: int,
|
|
previous_state: State | None = None):
|
|
self.state = State(state)
|
|
self.cost = cost # g
|
|
self.heuristic_cost = heuristic_cost # h
|
|
self.previous_state = previous_state
|
|
self.running_iterator: Iterator[State] | None = None
|
|
|
|
@property
|
|
def evaluated_cost(self):
|
|
return self.heuristic_cost + self.cost
|
|
|
|
def __lt__(self, other):
|
|
return self.evaluated_cost < other.evaluated_cost
|
|
|
|
def __repr__(self):
|
|
return f"{self.state} (g={self.cost}, h={self.heuristic_cost})"
|
|
|
|
|
|
def dfs_solve(original_state: State, final_state: State, max_cost: int = 2) -> int | bool:
|
|
open_list: list[StateNode] = [StateNode(original_state, 0, original_state.compare_with(final_state))]
|
|
next_max_cost: int | float = float("inf")
|
|
while open_list:
|
|
current_node = open_list[-1]
|
|
print(open_list, file=sys.stderr)
|
|
if current_node.state == final_state:
|
|
return True
|
|
|
|
if current_node.running_iterator is None:
|
|
current_node.running_iterator = iter(current_node.state.get_possible_steps())
|
|
continue
|
|
|
|
try:
|
|
new_state = next(current_node.running_iterator)
|
|
if new_state == current_node.previous_state:
|
|
continue
|
|
new_node = StateNode(new_state, current_node.cost + 1, new_state.compare_with(final_state),
|
|
current_node.state)
|
|
if new_node.evaluated_cost > max_cost:
|
|
next_max_cost = min(new_node.evaluated_cost, next_max_cost)
|
|
continue
|
|
open_list.append(new_node)
|
|
except StopIteration:
|
|
open_list.pop()
|
|
if TYPE_CHECKING:
|
|
return int(next_max_cost)
|
|
else:
|
|
return next_max_cost
|
|
|
|
|
|
def ida_star_solve(original_state: State, final_state: State) -> int:
|
|
max_cost = original_state.compare_with(final_state)
|
|
while True:
|
|
print(max_cost, "!!!!!!", file=sys.stderr)
|
|
result = dfs_solve(original_state, final_state, max_cost)
|
|
if result is True:
|
|
return max_cost
|
|
max_cost = result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
original_state = State(input())
|
|
final_state = State(123804765)
|
|
print(ida_star_solve(original_state, final_state))
|